SpringSecurity+JWT配置的坑以及源码分析
抛出问题:
之前在QQ上有位朋友问jwt的问题,开始的时候只给了他一个自己写的参考案例,以为整个过程就可以顺利配置并且可以愉快的使用,但是后面遇到了一个小坑,可能平时配置自定义AuthorizationServerConfigurerAdapter
的自定义父类的时候,一下子就入坑了,好现在先抛出代码然后分析,以下为部分核心配置代码:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
//通过TokenEnhancerChain增强器链将jwtAccessTokenConverter(转换成jwt)和jwtTokenEnhancer(往里面加内容加信息)连起来
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = Lists.newArrayList();
enhancerList.add(tokenEnhancer());
enhancerList.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(enhancerList);
endpoints
.tokenEnhancer(enhancerChain)
.tokenServices(tokenServices())
.accessTokenConverter(accessTokenConverter());
}
@Primary
@Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices=new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
//维持刷新token
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
public TokenStore tokenStore() {
TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
return tokenStore;
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey(jwtSigningKey);
return accessTokenConverter;
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
可是启动执行获取token接口返回值为:
{
"access_token": "35cdbd07-9b5e-40ab-b69f-8eacaa2c1fc7",
"token_type": "bearer",
"refresh_token": "db5aa1e5-512b-45a4-9d55-aa137d710f67",
"expires_in": 43199,
"scope": "read,write"
}
乍一看这个没啥问题,我明明配置的是jwt的accessTokenConverter
和tokenStore
可是启动程序生成的token怎么不是jwt呢,而是uuid的一串字符串,自己也没发现有啥问题,找呀找呀,时间就这么过去了......
源码分析:
通过跟踪源码分析TokenEndpoint
的接口方法postAccessToken
可以发现生成token的执行的方法为:
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
其中其核心生成token的类为DefaultTokenServices
执行的方法为createAccessToken
可以看到源码中生成token的方法为createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)
的私有方法,源码为:
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
//这个才是自定义token生成策略的关键
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
可以发现这边在createToken
的时候首先是使用UUID
来生成的,然后在最后一行
accessTokenEnhancer != null
采用的是accessTokenEnhancer
配置的token
生成策略,若为null
则直接返回生成的uuid
的token
.
失效原因分析:
源码的token
生成策略我们已经分析完成,现在让我们回过头来看看我们之前的配置代码TokenEnhancerChain
已经设置了我们的tokenStore
和accessTokenConverter
但是为啥没生效呢......?
可以看到我们在重写configure(AuthorizationServerEndpointsConfigurer endpoints)
这个方法的时候执行了一个命令endpoints. .tokenServices(tokenServices())
而tokenService()
方法我们配置的是什么呢?
@Primary
@Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices=new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
//维持刷新token
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
我们这边重new DefaultTokenServices()
,并且没有配置DefaultTokenServices
下的private TokenEnhancer accessTokenEnhancer;
这个属性,所以导致我们在DefaultTokenServices
类下面执行createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)
时最后面的accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
时accessTokenEnhancer为空,导致直接返回生成的UUID的token返回客户端。
解决方案:
解决方式可以采用一下两种方式:
- 在
configure(AuthorizationServerEndpointsConfigurer endpoints)
方法中去掉endpoints.tokenServices(tokenServices())
这个配置,删除tokenService配置,只保留endpoint的
配置。即:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
//通过TokenEnhancerChain增强器链将jwtAccessTokenConverter(转换成jwt)和jwtTokenEnhancer(往里面加内容加信息)连起来
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = Lists.newArrayList();
enhancerList.add(tokenEnhancer());
enhancerList.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(enhancerList);
endpoints
.tokenEnhancer(enhancerChain)
.accessTokenConverter(accessTokenConverter());
}
- 将
endpoints
配置tokenEnhancer
的配置代码删除,落到配置tokenService()
方法的配置中去即:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
endpoints
.tokenServices(tokenServices())
.accessTokenConverter(accessTokenConverter());
}
@Primary
@Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices=new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
//维持刷新token
defaultTokenServices.setSupportRefreshToken(true);
//通过TokenEnhancerChain增强器链将jwtAccessTokenConverter(转换成jwt)和jwtTokenEnhancer(往里面加内容加信息)连起来
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = Lists.newArrayList();
enhancerList.add(tokenEnhancer());
enhancerList.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(enhancerList);
defaultTokenServices.setTokenEnhancer(enhancerChain);
return defaultTokenServices;
}