Spring cloud oauth2 集成jwt后,通常我们获取用户信息只能获取到username,往往在实际开发我们需要的用户信息是userId或者说其他信息
check_token流程简述
我们就需要了解Spring cloud oauth2是如何check_token的,不懂得小伙伴可以移步到 spring cloud oauth2 check token源码解析
-
了解check_token流程得小伙伴就知道check_token得第一步是被OAuth2AuthenticationProcessingFilter过滤器拦截,过滤器会拿到request中token信息封装成Authentication对象,通过OAuth2AuthenticationManager调用tokenService获取到OAuth2Authentication对象
OAuth2Authentication对象中包含了客户端以及用户信息还有一些request请求信息
然后对客户端信息进行一系列得验证,最后当然是存入到SecurityContextHolder.Context中 -
tokenService又做了一些什么事嘞,tokenService会去tokenStore中获取到token信息,不同得tokenStore会有不同得获取方式,比如RedisTokenStore顾名思义就是从redis中获取token信息,那么JwtTokenStore当然是解密jwt token得到token信息
-
得到token信息后,会通过JwtAccessTokenConverter中extractAuthentication()方法去调用到 DefaultAccessTokenConverter.extractAuthentication()去获取用户信息以及将token信息中得客户端信息封装成OAuth2Request对象,最后在将OAuth2Request对象和用户信息汇总起来构建出一个OAuth2Authentication对象返回
-
DefaultAccessTokenConverter.extractAuthentication()是如何获取得用户信息嘞?说到这我们得压轴类也应该出现了,没错他就是DefaultUserAuthenticationConverter,从类名就可以看出来它负责用户信息转换,DefaultAccessTokenConverter.extractAuthentication()其实就是去调用了DefaultUserAuthenticationConverter获取用户信息,DefaultUserAuthenticationConverter会去获取到token信息中得用户名,然后判断userDetailsService是否为空,不为空则通过userDetailsService去查询用户信息,为空得话直接将username作为用户信息,这下终于水落石出了
DefaultUserAuthenticationConverter源码解析
上面我们说到DefaultUserAuthenticationConverter是压轴类,那么我们就来看一看它主要做了些什么事, 它最终要的方法就是extractAuthentication方法,通过方法我们可以看出来为什么默认情况下只有username返回了
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return defaultAuthorities;
}
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));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
扯了那么多的蛋,我们还是没有讲到怎么去自定义用户信息返回,没错你猜对了,下面我们就来讲一讲怎么自定义用户信息返回
自定义用户信息返回
思路讲解:原有的方法中判断userDetailsService是否为null,是不是我们将userDetailsService注入进来就可以实现了自定义用户信息返回,没错!!!
但是你想过没,JWT优势就是可以包含用户信息从而减少服务器存储token信息的压力,如果这样子去做,那何必用jwt嘞
我的思路是去继承DefaultUserAuthenticationConverter并实现UserAuthenticationConverter,重写它的extractAuthentication()方法
注意!注意!注意!,我的思路可实现是因为我在生成token的时候,用TokenEnhancer对token进行了增强,所以我的JWT token中包含了我需要的用户信息,我只需要在extractAuthentication方法中将信息出去来,封装成一个对象,放入到Authentication中就可以了
怎么用TokenEnhancer去增强token,可以参考spring cloud oauth 集成 JWT 之 token 增强这篇文章
接下来我们开始撸代码吧
- 自定义LwUserAuthenticationConverter继承DefaultUserAuthenticationConverter并实现UserAuthenticationConverter,重写它的extractAuthentication()方法
public class LwUserAuthenticationConverter extends DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
private Collection<? extends GrantedAuthority> defaultAuthorities = new ArrayList<>();
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
Map principal = (Map) map.get(SecurityConstants.USER_INFO);
String userId = MapUtil.getStr(principal, "userId");
String username = MapUtil.getStr(principal, "username");
String password = MapUtil.getStr(principal, "password");
String openid = MapUtil.getStr(principal, "openid");
String phone = MapUtil.getStr(principal, "phone");
String avatar = MapUtil.getStr(principal, "avatar");
LoginUser loginUser = new LoginUser(username, password, userId, phone, avatar, openid, authorities);
return new UsernamePasswordAuthenticationToken(loginUser, "N/A", authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return defaultAuthorities;
}
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));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
- 将JwtAccessTokenConverter类中的userTokenConverter属性替换成我们自定义的LwUserAuthenticationConverter
- 将TokenStore改为JwtTokenStore,并将JwtAccessTokenConverter注入到JwtTokenStore中
/**
* 存储策略
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* JWT转换器
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
DefaultAccessTokenConverter defaultConverter = new DefaultAccessTokenConverter();
defaultConverter.setUserTokenConverter(lwUserAuthenticationConverter());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(defaultConverter);
converter.setSigningKey("123456");
return converter;
}
/**
* 自定义用户身份验证转换器
* @param
*/
@Bean
public LwUserAuthenticationConverter lwUserAuthenticationConverter(){
return new LwUserAuthenticationConverter();
}
- 在资源服务器配置文件中设置存储策略
/**
* 注入token存取策略
* 开启无状态模式
*
* @param resource
*/
@Override
public void configure(ResourceServerSecurityConfigurer resource) throws Exception {
resource.tokenStore(tokenStore)
.stateless(true);
}
这样我们就大功告成了,
效果演示
没错,我们已经通过拿到定制的用户信息了