我们知道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"
}
我们可以发现这里并没有包含用户等关键信息,如果我们在此基础上扩展输出,直接可以通过认证接口获取到用户信息等,大大提高系统性能
我们在前面的文章分析知道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进行增强
TokenEnhancer.enhance 通过上下文中的用户信息来个性化Token
自定义TokenEnhancer增强器
@Component
public class CustomTokenEnhancer implements TokenEnhancer {private final static String CLIENT_CREDENTIALS = "client_credentials";@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {return accessToken;}final Map 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" }
}
利用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 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 returnValueHandlers = handlerAdapter.getReturnValueHandlers(); List 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; }
}
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public Object handlePostAccessTokenMethod(ProceedingJoinPoint joinPoint) throws Throwable { // 获取原有值,进行包装返回 Object proceed = joinPoint.proceed(); ResponseEntity responseEntity = (ResponseEntity) proceed; OAuth2AccessToken body = responseEntity.getBody(); return ResponseEntity .status(HttpStatus.OK) .body(R.ok(body)); }
}