OAuth + Security – 5 – Token存儲升級(資料庫、Redis)
- 2020 年 6 月 7 日
- 筆記
- OAuth+Security
PS:此文章為系列文章,建議從第一篇開始閱讀。
在我們之前的文章中,我們當時獲取到Token令牌時,此時的令牌時存儲在記憶體中的,這樣顯然不利於我們程式的擴展,所以為了解決這個問題,官方給我們還提供了其它的方式來存儲令牌,存儲到資料庫或者Redis中,下面我們就來看一看怎麼實現。
不使用Jwt令牌的實現
- 存儲到資料庫中(JdbcTokenStore)
使用資料庫存儲方式之前,我們需要先準備好對應的表。Spring Security OAuth倉庫可以找到相應的腳本://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql。該腳本為HSQL,所以需要根據具體使用的資料庫進行相應的修改,以MySQL為例,並且當前項目中,只需要使用到oauth_access_token和oauth_refresh_token數據表,所以將創建這兩個庫表的語句改為MySQL語句:
CREATE TABLE oauth_access_token (
token_id VARCHAR ( 256 ),
token BLOB,
authentication_id VARCHAR ( 256 ),
user_name VARCHAR ( 256 ),
client_id VARCHAR ( 256 ),
authentication BLOB,
refresh_token VARCHAR ( 256 )
);
CREATE TABLE oauth_refresh_token (
token_id VARCHAR ( 256 ),
token BLOB, authentication BLOB
);
然後我們需要去配置對應的認證伺服器,主要就是修改之前文章中TokenConfigure類中的tokenStore()方法:
// 同時需要注入數據源
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
同時需要添加jdbc的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
認證伺服器中的配置保持本系列第一章的配置不變,此處不再贅述。
其中若有不理解的地方,請參考該系列的第一篇文章
關於數據源的補充:
在我們項目中配置的數據源,可能不一定是使用的官方提供的格式,比如我們自定義的格式,或者使用第三方的數據源,那麼我們如何去配置呢?這裡以mybatis-plus的多數據源為例:
@Configuration
@EnableAuthorizationServer
public class FebsAuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {
......
@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
@Bean
public TokenStore tokenStore() {
DataSource dimplesCloudBase = dynamicRoutingDataSource.getDataSource("dimples_cloud_base");
return new JdbcTokenStore(febsCloudBase);
}
......
}
然後啟動項目,重新獲取Token進行測試,能正確的獲取到Token:
我們查看數據中,看是否已經存入相關資訊到資料庫中:
- 存儲到redis(RedisTokenStore)
令牌存儲到redis中相比於存儲到資料庫中來說,存儲redis中,首先在性能上有一定的提升,其次,令牌都有有效時間,超過這個時間,令牌將不可再用,而redis的可以給對應的key設置過期時間,完美切合需求,所有令牌存儲到redis中也是一種值得使用的方法。
首先,我們需要在項目中添加redis依賴,同時配置redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
新建配置類RedisConfigure
@Configuration
public class RedisConfigure{
@Bean
@ConditionalOnClass(RedisOperations.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key採用 String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的 key也採用 String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式採用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的 value序列化方式採用 jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
配置redis的連接
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
lettuce:
pool:
min-idle: 8
max-idle: 500
max-active: 2000
max-wait: 10000
timeout: 5000
這時我們啟動項目測試,會發現項目將會報錯:
Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
這是由於我們配置RedisConfigure時,使用到了一個ObjectMapper類,這個類需要我們引入Apache的一個工具包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
最後,我們需要在認證伺服器中配置token的存儲方式,還是同jdbc的配置,在tokenStore()方法中配置,打開我們的TokenConfigure類,然後做如下配置:
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
認證伺服器中的配置還是跟之前的一樣,保持不變,啟動項目,獲取Token測試:
我們查看Redis中,看是否已經存入相關資訊到資料庫中:
- 其它
我們打開TokenStore介面的實現類,會發現,還有一種JwkTokenStore,這種實際上就是將我們UUID格式令牌變成可以帶特殊含義的jwt格式的令牌,我們已經在第三篇文章中介紹過了,可以參考前面的文章。
但是我們需要明白一點的是,這種令牌還是存儲在記憶體中的,後期我們如何將其存儲到redis中是我們研究的方向。
在上面的token存儲到資料庫和存儲到redis中,我們會發現一個問題,那就是我們多次獲取令牌,但是其值是固定不變的,為了解決這個問題,我們可以使用如下方式解決:
@Bean
public TokenStore tokenStore() {
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// 解決每次生成的 token都一樣的問題
redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
return redisTokenStore;
}
使用JWT令牌的實現
在之前的所有使用方法中,我們要麼是將Token存儲在資料庫或者redis中的時候,令牌的格式是UUID的無意義字元串,或者是使用JWT的有意義字元串,但是確是將令牌存儲在記憶體中,那麼,我們現在想使用JWT令牌的同時,也想將令牌存儲到資料庫或者Redis中。我們該怎麼配置呢?下面我們以Redis存儲為例,進行探索:
首先,我們還是要使用到TokenConfigure中的JWT和Redis配置:
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//對稱秘鑰,資源伺服器使用該秘鑰來驗證
converter.setSigningKey(SIGNING_KEY);
return converter;
}
@Bean
public TokenStore tokenStore() {
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// 解決每次生成的 token都一樣的問題
redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
return redisTokenStore;
}
接下來,是配置認證伺服器,在認證伺服器中,跟之前的配置有一點區別,如下:
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
// 配置密碼模式管理器
.authenticationManager(authenticationManager)
// 配置授權碼模式管理器
.authorizationCodeServices(authorizationCodeServices())
// 令牌管理
// .tokenServices(tokenServices());
.accessTokenConverter(jwtAccessTokenConverter)
.tokenStore(tokenStore);
}
啟動項目,進行測試,獲取token
然後使用該令牌請求資源
至此,我們差不多完成了Token令牌的存儲和獲取
源碼傳送門: //gitee.com/dimples820/dimples-explore