本章任务,搭建OAuth2的资源服务器,什么是认证授权服务器什么是资源服务器,请参考链接,如果是谷歌浏览器,右键点翻译即可了解大概。资源服务器与认证授权服务器可以是一个服务器,也可以独立使用。
现在我所讲述的是独立使用,独立使用的一个好处在于,你的认证授权服务器是一个,但是你的资源服务器却可以是多个,这就形成了一对多的关系。就我个人理解来说(未有具体实战,如有错误请指出),而且如果你的情况是:分布式应用,那就更好办了。比如,对于当前架构,使用redis作为存储AccessToken的介质,你可以使用主从redis多节点存储AccessToken,部署多个认证授权服务器。这样认证授权服务器与资源服务器就形成了多对多的关系。
好了废话少说,开始:code show time now!
获取AccessToken接口
/**
* @Desc 认证登录接口(获取AccessToken)
*/
@RestController
@RequestMapping("/api/oauth2")
public class Oauth2Controller {
private static final Logger log = LoggerFactory.getLogger(Oauth2Controller.class);
/**
* OAuth2的密码授权模式
*/
@RequestMapping(value = "/passwordMode",method = RequestMethod.POST)
public Object accessToken(@RequestParam(value = "client_id") String client_id,
@RequestParam(value = "client_secret") String client_secret,
@RequestParam(value = "grant_type") String grant_type,
@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password
){
//补足:对dm5加密后的密码不足32位加零补齐
String fill = "";
if (password.length() < 32) {//下面的details的password的长度必须32位,所以非32位则,需要补足位数
int len = 32 - password.length();
fill = String.format("%0" + len + "d", 0);
}
//创建一个包含需要请求的资源实体以及认证信息集合的对象
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
//设置请求认证授权的服务器的地址
details.setAccessTokenUri(ApplicationSupport.getParamVal("oauth.token"));
//下面都是认证信息:所拥有的权限,认证的客户端,具体的用户
details.setScope(Arrays.asList("read", "write"));
details.setClientId(client_id);
details.setClientSecret(client_secret);
details.setUsername(username);
details.setPassword(fill + password);
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken accessToken = null;
try {
//获取AccessToken
// 1、(内部流程简介:根据上述信息,将构造一个前文一中的请求头为 "Basic Base64(username:password)" 的http请求
//2、之后将向认证授权服务器的 oauth/oauth_token 端点发送请求,试图获取AccessToken
accessToken = provider.obtainAccessToken(details, new DefaultAccessTokenRequest());
} catch (NullPointerException e) {
log.error("授权失败原因:{}", e.getMessage());
return "用户不存在";
}catch (Exception e){
log.error("授权失败原因:{}", e.getMessage());
return "创建token失败";
}
return accessToken;
}
/**
* Oauth2的受信任的客户端授权模式
*/
@RequestMapping(value = "/clientMode",method = RequestMethod.POST)
public Object getToken(@RequestParam(value = "client_id") String client_id,
@RequestParam(value = "client_secret") String client_secret,
@RequestParam(value = "grant_type") String grant_type
){
//创建一个包含需要请求的资源实体以及认证信息集合的对象
ClientCredentialsResourceDetails clientCredentials = new ClientCredentialsResourceDetails();
clientCredentials.setAccessTokenUri(ApplicationSupport.getParamVal("oauth.token"));
//下面都是认证信息:所拥有的权限,认证的客户端
clientCredentials.setScope(Arrays.asList("read", "write"));
clientCredentials.setClientId(client_id);
clientCredentials.setClientSecret(client_secret);
clientCredentials.setGrantType(grant_type);
ClientCredentialsAccessTokenProvider provider = new ClientCredentialsAccessTokenProvider();
OAuth2AccessToken accessToken = null;
try {
accessToken = provider.obtainAccessToken(clientCredentials, new DefaultAccessTokenRequest());
} catch (Exception e) {
e.printStackTrace();
return "获取AccessToken失败";
}
return accessToken;
}
}
检测AccessToken有效性的拦截器Oauth2Interceptor
/**
* 对AccessToken进行检测,当出现AccessToken失效或者非法时,将直接返回401,未授权错误
*/
public class Oauth2Interceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String accessToken = request.getParameter("access_token");
OAuth2AccessToken oauth2AccessToken = Oauth2Utils.checkTokenInOauth2Client(accessToken);
if (oauth2AccessToken==null){//非法的Token值
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ResponseUtils.responseData(response,"非法的Token!");
return false;
}else if (oauth2AccessToken.isExpired()){//token失效
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ResponseUtils.responseData(response,"Token失效,请重新登录!");
return false;
}
return true;
}
}
注册拦截器InterceptorRegisterConfiguration
@Configuration
@EnableWebMvc //开启spring mvc的相关默认配置
public class InterceptorRegisterConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Oauth2Interceptor())
.excludePathPatterns("/api/oauth2/**");//添加Oauth2Interceptor,除了/api/oauth2/**下的接口都需要进行 AccessToken 的校验
}
}
工具类Oauth2Utils、ResponseUtils、ApplicationSupport
/**
* 获取Spring容器管理的Bean对象,应用中配置参数
**/
/**
* 工具类
*/
public class Oauth2Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(Oauth2Utils.class);
//检查AccessToken有效性的url(认证授权服务器的url地址)
private static String checkTokenUrl ;
static {
checkTokenUrl = ApplicationSupport.getParamVal("oauth.check_token");
}
/**
* 客户端申请校验
* @param tokenValue
* @return
*/
public static OAuth2AccessToken checkTokenInOauth2Client(String tokenValue){
if (StringUtils.isEmpty(tokenValue)) {
return null;
}
try {
RestTemplate restTemplate = new RestTemplate();
OAuth2AccessToken oAuth2AccessToken = restTemplate.getForObject(checkTokenUrl+"?token="+tokenValue, OAuth2AccessToken.class);
return oAuth2AccessToken;
}catch (Exception e){
LOGGER.error("checkTokenInOauth2Client failure:",e);
return null;
}
}
}
@Component
public class ApplicationSupport implements DisposableBean, ApplicationContextAware {
private static ApplicationContext applicationContext;
// 获取配置文件参数值
public static String getParamVal(String paramKey){
return applicationContext.getEnvironment().getProperty(paramKey);
}
// 获取bean对象
public static Object getBean(String name) {
Assert.hasText(name);
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void destroy() throws Exception {
applicationContext = null;
}
}
一个需要合法的AccessToken的后端接口
/**
* 测试接口
*/
@RestController
@RequestMapping("/api")
public class TestController {
@RequestMapping("/test")
public String test(){
return "success";
}
}
配置文件application.yml
security:
basic:
enabled: false # 是否开启基本的鉴权,默认为true。 true:所有的接口默认都需要被验证,将导致 拦截器[对于 excludePathPatterns()方法失效]
server:
context-path: /oauth2-client
port: 8051
---
spring:
application:
name: oauth2-client
datasource: #数据源的配置
url: jdbc:mysql://127.0.0.1:3306/redis-oauth2?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
jpa: #jpa的支持:hibernate的相关配置
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
database: MYSQL
openInView: true
show_sql: true
generate-ddl: true #(false)
hibernate:
ddl-auto: update #(none)
oauth: #oauth2-server认证授权服务器的url配置,在获取AccessToken以及检测AccessToken中会用到
token: http://127.0.0.1:8050/oauth2-server/oauth/token
check_token: http://localhost:8050/oauth2-server/oauth/check_token #检查AccessToken有效性的url(认证授权服务器的url地址),获取 AccessToken 对象
启动类
@SpringBootApplication
@EnableOAuth2Client
public class Oauth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ClientApplication.class, args);
}
}
上面就是一个完整的OAuth2服务器中资源服务器的整个代码,是不是很简单。当然,代码中尚有可以根据环境调整的部分,比如,在拦截器中,我们可以直接用key(tokenValue)从redis中获取AccessToken的完整信息,然后判断是否存在,是否失效,这也是一种策略。其次,在获取AccessToken的value值之后,你也可以自定义一个key-value存储当前认证登陆用户的有用信息到redis中(可以与认证授权服务器使用同一个库,也可以不同),并设置失效时间,然后在拦截器中先再redis中校验,最后如有必要再去认证服务器中校验。此处,我是直接发送http请求去远端校验的。
测试流程
1、先启动认证授权服务器oauth-server
2、后启动资源服务器
3、向资源服务器发送获取AccessToken的请求
4、使用获取的AccessToken向测试接口发送请求
测试结果
密码认证模式获取AccessToken如下图:
密码授权模式
受信任的客户端模式获取AccessToken如下图:
受信任的客户端模式
无AccessToken访问测试接口如下图:
无AccessToken
使用AccessToken访问测试接口如下图:
使用AccessToken
大功告成
现在,Spring-Security-OAuth2的认证授权服务器和资源服务器到此全部搭建完毕。
基于此资源服务器可以为移动端(Android和iOS)、网页端提供数据接口。上述系列文章,如有错误,请诸多指教!
网友评论