述
默认的token生成规则其实就是一个UUID,就是一个随机的字符串,然后存到redis中去,使用JWT的话,token中可以存放一些信息,我们服务端也不需要保存这个token, 服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证
使用JWT,在分布式系统中,很好地解决了单点登录问题,很容易解决了session共享的问题.但是是无法作废已颁布的令牌/不易应对数据过期,因为 token 并没有保存到服务端, 下面来看一下如何去配置JWT
配置JWT
TokenStoreConfig
这个类中要做一些修改,之前我们只在这个类里面配置了 redis 的存储,现在把 JWT 的配置也加上,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34@Configuration
public class TokenStoreConfig {
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
@ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="redis")
public TokenStore redisTokenStore(){
return new RedisTokenStore(connectionFactory);
}
@Configuration
@ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="jwt", matchIfMissing = true)
public static class JwtTokenConfig{
@Autowired
private SecurityProperties securityProperties;
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
// token生成中的一些处理
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(securityProperties.getOAuth2().getJwtTokenSignKey());
return converter;
}
}
}
这里首先是一个内部的静态类 JwtTokenConfig
用来配置 JWT
的一些配置,第一个方法 jwtTokenStore()
就是配置token的存储,然后这里需要一个 JwtAccessTokenConverter
因为 TokenStore
只管 Token 的存储,生成规则还需要配置,所以 jwtAccessTokenConverter()
就是用来做一些 Token 的处理
这个类上有一个注解 @ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="jwt", matchIfMissing = true)
意思就是,在配置中有 core.security.oAuth2.tokenStore
这个配置,而且值是 jwt
的话,就生效,最后有一个 matchIfMissing = true
,这个表示, 如果配置中没有这个配置的话,也生效
上面的 redisTokenStore 也加了这个注解,但是没有 matchIfMissing
默认是 false, 总的配置就是如果在配置中没有指定哪种 tokenStore 的话,就默认的用 jwt ,如果想要使用 redis 存储的话,必须明确的指定 core.security.oAuth2.tokenStore: redis
最后认证服务的配置中还需要做一些修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// ... 省略其他配置
// 只有配置使用JWT的时候才会生效
if (jwtAccessTokenConverter != null) {
endpoints.accessTokenConverter(jwtAccessTokenConverter);
}
}
// ... 省略其他代码
}
这里,就是给 endpoints
指定一下 JwtAccessTokenConverter
就可以了
测试
请求 Token 还是跟之前的方式一样的, 如下:
可以看到这里的token就是使用jwt了
在 jwt.io 中可以把刚刚生成的token解析一下,内容如下:
这个就是我们生成的 JWT 中包含的信息
TokenEnhancer 的使用
TokenEnhancer
是一个增强器,JWT 中是可以放一些我们自定义的信息的,如果要加入一些我们自己的信息的话,就得使用 TokenEnhancer
还是修改 TokenStoreConfig
,在 JwtTokenConfig
这个内部类中,加一个配置1
2
3
4
5@Bean
@ConditionalOnBean(TokenEnhancer.class)
public TokenEnhancer jwtTokenEnhancer(){
return new MyJwtTokenEnhancer();
}
然后 MyJwtTokenEnhancer
代码如下:1
2
3
4
5
6
7
8
9
10
11public class MyJwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>(1);
info.put("custom", "test");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
这里加了 @ConditionalOnBean
,是必须存在一个TokenEnhancer
的时候,才被创建, 之前的 JwtAccessTokenConverter
也是一个 TokenEnhancer
最后,认证服务器配置类 MyAuthorizationServerConfig
中,需要修改一下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Autowired(required = false)
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.userDetailsService(userDetailsService);
endpoints.tokenStore(tokenStore);
// 只有配置使用JWT的时候才会生效
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
测试
获取token,然后解析结果如下:
自定义数据解析
这里 Spring 在解析JWT的时候会解析成一个 Authentication
对象,并不会解析我们上面设置的自定义字段,这个还需要我们自己去解析
demo 项目中增加一个 jwt 解析的依赖
1 | <dependency> |
然后再 /user/me
这个接口中做解析,代码如下:1
2
3
4
5
6
7
8
9
10
11
12@GetMapping("/me")
public Object me(Authentication authentication, HttpServletRequest request) throws UnsupportedEncodingException {
// 从请求头中获取到token
String jwtToken = StringUtils.substringAfter(request.getHeader("Authorization"), AUTHORIZATION_PREFIX);
log.info("请求头中的token:{}", jwtToken);
// 获取配置中的 jwtTokenSignKey
String jwtTokenSignKey = securityProperties.getOAuth2().getJwtTokenSignKey();
Claims claims = Jwts.parser().setSigningKey(jwtTokenSignKey.getBytes("UTF-8")).parseClaimsJws(jwtToken).getBody();
return claims;
}
效果如下: