1. 程式人生 > >OAuth + Security - 5 - Token儲存升級(資料庫、Redis)

OAuth + Security - 5 - Token儲存升級(資料庫、Redis)

> PS:此文章為系列文章,建議從第一篇開始閱讀。 在我們之前的文章中,我們當時獲取到Token令牌時,此時的令牌時儲存在記憶體中的,這樣顯然不利於我們程式的擴充套件,所以為了解決這個問題,官方給我們還提供了其它的方式來儲存令牌,儲存到資料庫或者Redis中,下面我們就來看一看怎麼實現。 #### 不使用Jwt令牌的實現 - 儲存到資料庫中(JdbcTokenStore) 使用資料庫儲存方式之前,我們需要先準備好對應的表。Spring Security OAuth倉庫可以找到相應的指令碼:[https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql](https://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的依賴: ``` org.springframework.boot
spring-boot-starter-jdbc
``` 認證伺服器中的配置保持本系列第一章的配置不變,此處不再贅述。 > 其中若有不理解的地方,請參考該系列的第一篇文章 關於資料來源的補充: 在我們專案中配置的資料來源,可能不一定是使用的官方提供的格式,比如我們自定義的格式,或者使用第三方的資料來源,那麼我們如何去配置呢?這裡以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: ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071112554.png) 我們檢視資料中,看是否已經存入相關資訊到資料庫中: ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/t_2006071113003.png?a=1591528475702) - 儲存到redis(RedisTokenStore) 令牌儲存到redis中相比於儲存到資料庫中來說,儲存redis中,首先在效能上有一定的提升,其次,令牌都有有效時間,超過這個時間,令牌將不可再用,而redis的可以給對應的key設定過期時間,完美切合需求,所有令牌儲存到redis中也是一種值得使用的方法。 首先,我們需要在專案中新增redis依賴,同時配置redis ``` org.springframework.boot
spring-boot-starter-data-redis
``` 新建配置類RedisConfigure ``` @Configuration public class RedisConfigure{ @Bean @ConditionalOnClass(RedisOperations.class) public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer 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的一個工具包 ``` org.apache.commons
commons-pool2
``` 最後,我們需要在認證伺服器中配置token的儲存方式,還是同jdbc的配置,在tokenStore()方法中配置,開啟我們的TokenConfigure類,然後做如下配置: ``` @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); } ``` 認證伺服器中的配置還是跟之前的一樣,保持不變,啟動專案,獲取Token測試: ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071112554.png) 我們檢視Redis中,看是否已經存入相關資訊到資料庫中: ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071312275.png) - 其它 我們開啟TokenStore介面的實現類,會發現,還有一種JwkTokenStore,這種實際上就是將我們UUID格式令牌變成可以帶特殊含義的jwt格式的令牌,我們已經在第三篇文章中介紹過了,可以參考前面的文章。 但是我們需要明白一點的是,這種令牌還是儲存在記憶體中的,後期我們如何將其儲存到redis中是我們研究的方向。 ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071318126.png) 在上面的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 ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071505587.png) ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071507139.png) 然後使用該令牌請求資源 ![image](https://images.cnblogs.com/cnblogs_com/reroyalup/1776738/o_2006071506038.png) 至此,我們差不多完成了Token令牌的儲存和獲取 原始碼傳送門: [https://gitee.com/dimples820/dimples-explore](https://gitee.com/dimples820/dimples-explore/blob/master/dimples-security/src/main/java/com/dimples/security/configure/DimplesAuthorizationServerConfigurati