1.令牌的存储方式
令牌有多种存储方式,每种方式都是实现了 TokenStore 接口
- 存储在本机内存: InMemoryTokenStore
- 存储在数据库: JdbcTokenStore
- JWT: JwtTokenStore,Json Web Token 不会存储在任何介质中,不过我还是不推荐这种做法啊,并发 2w 以后会有问题
- 存储在 Redis: RedisTokenStore
2.使用 Redis 存储令牌
2.1 pom添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.2 application配置
spring:
application:
name: security-demo
redis:
host: 127.0.0.1
port: 6379
# password: 没有不用写
lettuce:
# 连接池配置
pool:
# 连接池中的最小空闲连接,默认 0
min-idle: 0
# 连接池中的最大空闲连接,默认 8
max-idle: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制),默认 -1ms
max-wait: -1ms
# 连接池最大连接数(使用负值表示没有限制),默认 8
max-active: 8
2.3 Redis存储令牌配置
@Configuration
public class RedisTokenStoreConfig {
// 注入 RedisConnectionFactory,用于连接 Redis
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
}
2.4 指定存储策略为Redis
- 在认证服务器配置中指定令牌的存储策略为Redis
- AuthorizationServerConfig.class
// 注入redisTokenStore
@Resource(name = "redisTokenStore")
private TokenStore tokenStore;
// 使用密码模式需要配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
// 设置为redis 配置令牌存储策略
.tokenStore(tokenStore);
}
... 省略
2.5 运行项目
- 127.0.0.1:8666/oauth/token
- 进行获取令牌操作,可以发现令牌已经被存储到Redis中。
3. JWT存储Token
3.1 JWT 简介
JWT是JSON WEB TOKEN的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。
- JWT token的格式:header.payload.signature;
- header中用于存放签名的生成算法;
{
"alg": "HS256",
"typ": "JWT"
}
- payload中用于存放数据,比如过期时间、用户名、用户所拥有的权限等;
{
"exp": 1572682831,
"user_name": "macro",
"authorities": [
"admin"
],
"jti": "c1a0645a-28b5-4468-b4c7-9623131853af",
"client_id": "admin",
"scope": [
"all"
]
}
- signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败。
3.2 JWT存储令牌的配置
@Configuration
public class JwtTokenStoreConfig {
@Primary
@Bean("jwtTokenStore")
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("test_key");//配置JWT使用的秘钥
return accessTokenConverter;
}
}
3.3 指定令牌存储策略JWT
- AuthorizationServerConfig.class
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
// 注入redisTokenStore
/* @Resource(name = "redisTokenStore")
private TokenStore tokenStore;*/
// 改为JWT
@Resource(name = "jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
// 使用密码模式需要配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
// 设置为redis 配置令牌存储策略
.tokenStore(tokenStore)
// 将值转为jwt格式
.accessTokenConverter(jwtAccessTokenConverter);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin")//配置client_id
.secret(passwordEncoder.encode("admin123456"))//配置client_secret
.accessTokenValiditySeconds(3600)//配置访问token的有效期
.refreshTokenValiditySeconds(864000)//配置刷新token的有效期
.redirectUris("http://www.baidu.com")//配置redirect_uri,用于授权成功后跳转
.scopes("all") // 配置申请的权限范围,授权页面会显示
.authorizedGrantTypes("authorization_code","password");//配置grant_type,表示授权类型 ,"refresh_token"
}
}
3.4 运行
发现获取到的令牌已经变成了JWT令牌,将access_token拿到https://jwt.io/ 网站上去解析下可以获得其中内容。
4. 刷新令牌
在Spring Cloud Security 中使用oauth2时,如果令牌失效了,可以使用刷新令牌通过refresh_token的授权模式再次获取access_token。
- 只需修改认证服务器的配置,添加refresh_token的授权模式即可。
- 这里使用 redis的方式
- AuthorizationServerConfig.class
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin")//配置client_id
.secret(passwordEncoder.encode("admin123456"))//配置client_secret
.accessTokenValiditySeconds(3600)//配置访问token的有效期
.refreshTokenValiditySeconds(864000)//配置刷新token的有效期
.redirectUris("http://www.baidu.com")//配置redirect_uri,用于授权成功后跳转
.scopes("all") // 配置申请的权限范围,授权页面会显示
.autoApprove(true) //自动授权配置
.authorizedGrantTypes("authorization_code","password","refresh_token");//配置grant_type,表示授权类型
}
- redis中也多了一些key
5. 扩展JWT中存储的内容
有时候我们需要扩展JWT中存储的内容,这里我们在JWT中扩展一个key为enhance,value为enhance info的数据。
- 继承TokenEnhancer实现一个JWT内容增强器:
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("enhance", "enhance info");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
- 创建一个JwtTokenEnhancer实例:
@Configuration
public class JwtTokenStoreConfig {
//省略代码...
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
}
- 在认证服务器配置中配置JWT的内容增强器:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 使用密码模式需要配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer); //配置JWT的内容增强器
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore) //配置令牌存储策略
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
//省略代码...
}
- 运行项目后使用密码模式来获取令牌,之后对令牌进行解析,发现已经包含扩展的内容。
{
"user_name": "macro",
"scope": [
"all"
],
"exp": 1572683821,
"authorities": [
"admin"
],
"jti": "1ed1b0d8-f4ea-45a7-8375-211001a51a9e",
"client_id": "admin",
"enhance": "enhance info"
}