一 token的个性化输出
我们知道token默认的输出格式是:
{
"access_token": "21bd6b0b-0c24-40d1-8928-93274aa1180f",
"token_type": "bearer",
"refresh_token": "2c38965b-d4ce-4151-b88d-e39f278ce1bb",
"expires_in": 3599,
"scope": "all read write"
}
我们可以发现这里并没有包含用户等关键信息,如果我们在此基础上扩展输出,直接可以通过认证接口获取到用户信息等,大大提高系统性能
1.1 源码分析
我们在前面的文章分析知道token是通过DefaultTokenServices
来生成的我们看看createAccessToken
核心逻辑
// 默认刷新token 的有效期
private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.
// 默认token 的有效期
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(uuid);
token.setExpiration(Date)
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
在拼装好token对象后会调用认证服务器配置TokenEnhancer( 增强器)
来对默认的token进行增强
1.2 个性化token
TokenEnhancer.enhance 通过上下文中的用户信息来个性化Token
自定义TokenEnhancer增强器
@Component
public class CustomTokenEnhancer implements TokenEnhancer {
private final static String CLIENT_CREDENTIALS = "client_credentials";
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {
return accessToken;
}
final Map<String, Object> additionalInfo = new HashMap<>(8);
User user = (User) authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("username",user.getUsername());
// todo 自定义user实现自己想要扩展信息
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
配置
增强结果
二 格式化输出
在一些场景下我们需要自定义一下返回报文的格式,例如使用R 对象返回,全部包含code业务码信息」
{
"code":1,
"msg":"",
"data":{
"access_token":"e6669cdf-b6cd-43fe-af5c-f91a65041382",
"token_type":"bearer",
"refresh_token":"da91294d-446c-4a89-bdcf-88aee15a75e8",
"expires_in":43199,
"scope":"server"
}
}
2.1 HandlerMethodReturnValueHandler
利用Spring MVC 提供给我们修改方法返回值的接口
public class FormatterToken implements HandlerMethodReturnValueHandler {
private static final String POST_ACCESS_TOKEN = "postAccessToken";
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 判断方法名是否是 oauth2 的token 接口,是就处理
return POST_ACCESS_TOKEN.equals(Objects
.requireNonNull(returnType.getMethod()).getName());
}
// 获取到返回值然后使用 R对象统一包装
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer container, NativeWebRequest request) throws Exception {
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity) returnValue;
OAuth2AccessToken body = responseEntity.getBody();
HttpServletResponse response = request.getNativeResponse(HttpServletResponse.class);
assert response != null;
WebUtils.renderJson(response, R.ok(body));
}
}
注入FormatterToken,一定要这么处理,不要直接使用 MVCconfig 注入,保证此Handler比 SpringMVC 默认的提前执行。
public class FormatterTokenAutoConfiguration implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() {
RequestMappingHandlerAdapter handlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>();
newHandlers.add(new FormatterToken());
assert returnValueHandlers != null;
newHandlers.addAll(returnValueHandlers);
handlerAdapter.setReturnValueHandlers(newHandlers);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
2.2 aop 拦截增强 /oauth/token 接口
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public Object handlePostAccessTokenMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取原有值,进行包装返回
Object proceed = joinPoint.proceed();
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>) proceed;
OAuth2AccessToken body = responseEntity.getBody();
return ResponseEntity
.status(HttpStatus.OK)
.body(R.ok(body));
}
}