前言
之前写了一大堆关于SpringSecurity OAuth2的相关文章,本以为可以告一段落了,但是有了解到一个新东西,之前没注意到的,就是UserAuthenticationConverter,本章就来看看这是个啥玩意!我们知道授权服可以提供两种格式的Token,一种是默认的字符串,另一种是JWT格式的字符串,关于JWT格式Token我这里就不多逼逼是什么意思勒,我们认证服采用JWT格式主要就是为了提高Token校验的性能,如果我们采用默认的字符串的话默认需要调用认证服进行token检查,就是通过Http请求的方式调用认证服中的接口,那么这样大大限制了我们鉴权,我们采用JWT的话那么就可以让资源服进行本地鉴权,不用通过Http方式调用授权服进行Token的认证,那么这些都是没问题的,这也是本文的铺垫,我们知道JWT可以自包含一些数据的,那么我们在生成Token的时候就可以自定义一些数据进去,那么这部分之前文章中也有写过SpringSecurityOAuth2采用JWT生成Token的模式自定义JWT数据,那么我们将数据是放入到Token中了,也能将其获取到,SpringSecurity OAuth使用JWT替换默认Token这篇文章中有怎么获取,但是回过头看过去的文章感觉有点lol,那么本文我们就通过UserAuthenticationConverter玩意让我们更高级的获取JWT中的自定义数据!之前那种方式是通过解析Token中的字符串来实现的,那么我们高级点的办法突破口是在Principal,这里先提前透露一下
默认情况
默认获取Principal
@GetMapping(value = "/getPrincipal")
public R getPrincipal(Authentication authentication) {
return R.ok(authentication.getPrincipal());
}
这里得到的是用户名,对默认就是只包含用户名,我们看看解析流程
解析流程
OAuth2AuthenticationProcessingFilter
请求先是来到我们的老熟人OAuth2AuthenticationProcessingFilter
OAuth2AuthenticationManager
DefaultTokenServices
DefaultAccessTokenConverter
注意这里就已经得到了我们的自定义数据了!
回到主流程
DefaultAccessTokenConverter
DefaultUserAuthenticationConverter
这里也就是核心了,至于我们的principal中为什么只有用户名,核心点就在这里!理论上我们只需要将解析是的principal更换成我们自定义的对象,那么我们获取principal的时候就可以同理得到!
改造
创建CustomUserAuthenticationTokenConverter
public class CustomUserAuthenticationTokenConverter implements UserAuthenticationConverter {
private static final String N_A = "N/A";
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
JSONObject jsonObject = new JSONObject();
//自定义对象===>实际上这个对象是从map中获取对应的值,然后组装成自己需要的用户对象即可
jsonObject.put("aaa", "aaa");
jsonObject.put("bbb", "bbb");
return new UsernamePasswordAuthenticationToken(jsonObject, N_A, authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
}
return AuthorityUtils.NO_AUTHORITIES;
}
}
创建CustomResourceServiceTokenServices
public class CustomResourceServiceTokenServices implements ResourceServerTokenServices {
@Setter
private TokenStore tokenStore;
@Setter
private DefaultAccessTokenConverter defaultAccessTokenConverter;
@Setter
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
UserAuthenticationConverter userTokenConverter = new CustomUserAuthenticationTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(userTokenConverter);
Map<String, ?> map = jwtAccessTokenConverter.convertAccessToken(readAccessToken(accessToken), oAuth2Authentication);
return defaultAccessTokenConverter.extractAuthentication(map);
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
return tokenStore.readAccessToken(accessToken);
}
}
配置ResourceServerTokenServices
@Configuration
public class ResourceServerTokenServicesConfig {
@Autowired
private TokenStore tokenStore;
@Autowired(required = false)//required,只有在Jwt模式下才生效,正常情况下是没有这个类的
private JwtAccessTokenConverter jwtAccessTokenConverter;
//令牌管理服务
@Bean
public ResourceServerTokenServices tokenService() {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter =new CustomUserAuthenticationTokenConverter();
accessTokenConverter.setUserTokenConverter(userTokenConverter);
CustomResourceServiceTokenServices tokenServices = new CustomResourceServiceTokenServices();
tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
tokenServices.setTokenStore(tokenStore);
tokenServices.setJwtAccessTokenConverter(jwtAccessTokenConverter);
return tokenServices;
}
}
注入ResourceServerTokenServices
@Autowired
private ResourceServerTokenServices tokenService;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
log.info("RESOURCE_ID===>" + RESOURCE_ID);
resources.resourceId(RESOURCE_ID)//资源 id
.authenticationEntryPoint(customAuthenticationEntryPointHandler)//匿名用户访问无权限资源时的异常处理器
.accessDeniedHandler(customAccessDeniedHandler)//认证过的用户访问无权限资源时的异常处理器
.tokenServices(tokenService)
.stateless(true);
}
重启测试!
搞定了!其实这里我觉得是有点徒劳,真的,因为在我写这篇文章的时候注意到之前代码中有如下这些代码!
文章链接SpringSecurityOAuth2获取JWT中的数据
注意
在编写CustomResourceServiceTokenServices的时候,上面代码可以更加严谨点,我们看看默认实现的
建议也加上这些判断!
否则即使accessToken、result为空,或者解析失败那么也不会被我们的异常处理器捕获,这里指的异常处理器是
注意注意!!!
写在最后
这种写法后面经过本人实际业务开发调试,其实觉得有点鸡肋,而且带来很多开发上的问题,包括性能的损失!
1.丢失details
也就是defaultAccessTokenConverter.extractAuthentication(map);行代码执行完和SpringSecurity OAuth2默认执行完获取到的OAuth2Authentication中的数据是不一样的,会丢失details!
2.性能损耗
通过追溯上面丢失details的问题,源码级别的调试发现框起来的两段代码出现重复执行的问题,我们做这个UserAuthenticationConverter的操作,无非就是扩展principal对象的,实际也就是这部分代码调用开始扩展的!
通过这个方法,就可以执行到扩展代码
然而就是这里的extractAuthentication方法在OAuth2Authentication result = tokenStore.readAuthentication(accessToken);处就已经执行过了
只不过userTokenConverter.extractAuthentication(map);
调用的是SpringSecurity OAuth2默认的实现罢了!