本文就來說述一下spring security oauth2使用redis來存儲token的配置及在redis中的存儲結構java
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
@Configuration @EnableAuthorizationServer public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisConnectionFactory connectionFactory; @Override public void configure( AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .tokenStore(tokenStore()); } @Bean public TokenStore tokenStore() { RedisTokenStore redis = new RedisTokenStore(connectionFactory); return redis; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("demoApp") .secret("123456") .authorizedGrantTypes("password", "authorization_code"); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } }
這裏配置了redis token storeredis
固然配置文件須要指定redis地址spring
spring.redis.url=redis://localhost:6379
root@d8bfc99e9e07:/data# redis-cli --raw 127.0.0.1:6379> keys * auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f uname_to_access:demoApp:demoUser1 auth:8c1441db-6fac-4c57-9cc9-7c5adaafc18a client_id_to_access:demoApp access:8c1441db-6fac-4c57-9cc9-7c5adaafc18a
能夠看到這裏存了5個key數據庫
127.0.0.1:6379> type auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f string 127.0.0.1:6379> get auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f ��srCorg.springframework.security.oauth2.common.DefaultOAuth2AccessToken ��6��LadditionalInformationtLjava/util/Map;L expirationtLjava/util/Date;L refreshTokent?Lorg/springframework/security/oauth2/common/OAuth2RefreshToken;LscopetLjava/util/Set;L tokenTypetLjava/lang/String;Lvalueq~xpsrjava.util.Collections$EmptyMapY6�Z���xpsrjava.util.Datehj�KYtx`3�}xpsr%java.util.Collections$UnmodifiableSet��я��Uxr,java.util.Collections$UnmodifiableCollectionB��^�LctLjava/util/Collection;xpsrjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw ?@t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
這個key的命名結構是auth_to_access:OAuth2Authentication相關信息的加密值,默認是md5加密
具體存儲的是OAuth2AccessToken的序列化的值session
127.0.0.1:6379> type uname_to_access:demoApp:demoUser1 list 127.0.0.1:6379> lrange uname_to_access:demoApp:demoUser1 0 -1 ��srCorg.springframework.security.oauth2.common.DefaultOAuth2AccessToken ��6��LadditionalInformationtLjava/util/Map;L expirationtLjava/util/Date;L refreshTokent?Lorg/springframework/security/oauth2/common/OAuth2RefreshToken;LscopetLjava/util/Set;L tokenTypetLjava/lang/String;Lvalueq~xpsrjava.util.Collections$EmptyMapY6�Z���xpsrjava.util.Datehj�KYtx`3�}xpsr%java.util.Collections$UnmodifiableSet��я��Uxr,java.util.Collections$UnmodifiableCollectionB��^�LctLjava/util/Collection;xpsrjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw ?@t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
這個key的命名是uname_to_access:clientId:userId
value的結構是list,存儲token的序列化值app
127.0.0.1:6379> type auth:8c1441db-6fac-4c57-9cc9-7c5adaafc18a string 127.0.0.1:6379> get auth:8c1441db-6fac-4c57-9cc9-7c5adaafc18a ��srAorg.springframework.security.oauth2.provider.OAuth2Authentication�@ storedRequestt<Lorg/springframework/security/oauth2/provider/OAuth2Request;LuserAuthenticationt2Lorg/springframework/security/core/Authentication;xrGorg.springfauthenticatedLity.authentication.AbstractAuthenticationTokenӪ(~nGdZ authoritiestLjava/util/Collection;LdetailstLjava/lang/Object;xpsr&java.util.Collections$UnmodifiableList�%1��LlisttLjava/util/List;xr,java.util.Collections$UnmodifiableCollectionB��^�Lcq~xpsrjava.util.ArrayListx����a�IsizexpwsrBorg.springframework.security.core.authority.SimpleGrantedAuthority�LroletLjava/lang/String;xptUSERxq~ psr:org.springframework.security.oauth2.provider.OAuth2RequestapprovedL authoritiesq~L extensionstLjava/util/Map;L redirectUriq~Lrefresht;Lorg/springframework/security/oauth2/provider/TokenRequest;L responseTypesq~xr8org.springframework.security.oauth2.provider.BaseRequest6(z>�qi�clientIdq~LrequestParametersq~Lscopeq~xptdemoAppsr%java.util.Collections$UnmodifiableMap��t�BLmq~xpsrjava.util.HashMap���`�F loadFactorI thresholdxp?@ tcodetbt4UxDt response_typetcodetation_codet client_secrett123456tdirclient_idtdemoAppxsr%java.util.Collections$UnmodifiableSet��я��Uxq~ srjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw ?@t read_contactsxsq~+w ?@xsq~?@xthttp://localhost:8081/callbackpsq~+w ?@xsq~+w ?@q~!xsrOorg.springframework.security.authentication.UsernamePasswordAuthenticationToken�L credentialsq~L principalq~xq~sq~sq~ wq~xq~7srHorg.springframework.securiremoteAddressq~Lation.WesessionIdq~xpt0:0:0:0:0:0:0:1t 1C57DE920EFB42EEC0387D162D91B30Apsr2org.springframework.security.core.userdetails.User�ZaccountNonExpiredZaccountNonLockedZcredentialsNonExpiredZenabledL authoritiesq~passwordq~usernameq~xpsq~(srjava.util.TreeSetݘP���[xpsrForg.springframework.security.core.userdetails.User$AuthorityComparator�xpwq~xpt demoUser1
這個key的命名結構是auth:token值
value的結構是string,存儲的是OAuth2Authentication的序列化值maven
127.0.0.1:6379> type client_id_to_access:demoApp list 127.0.0.1:6379> lrange client_id_to_access:demoApp 0 -1 ��srCorg.springframework.security.oauth2.common.DefaultOAuth2AccessToken ��6��LadditionalInformationtLjava/util/Map;L expirationtLjava/util/Date;L refreshTokent?Lorg/springframework/security/oauth2/common/OAuth2RefreshToken;LscopetLjava/util/Set;L tokenTypetLjava/lang/String;Lvalueq~xpsrjava.util.Collections$EmptyMapY6�Z���xpsrjava.util.Datehj�KYtx`3�}xpsr%java.util.Collections$UnmodifiableSet��я��Uxr,java.util.Collections$UnmodifiableCollectionB��^�LctLjava/util/Collection;xpsrjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw ?@t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
這個key命名結構是client_id_to_access:clientId
value結構是list,存儲OAuth2AccessToken的序列化值ide
127.0.0.1:6379> type access:8c1441db-6fac-4c57-9cc9-7c5adaafc18a string 127.0.0.1:6379> get access:8c1441db-6fac-4c57-9cc9-7c5adaafc18a ��srCorg.springframework.security.oauth2.common.DefaultOAuth2AccessToken ��6��LadditionalInformationtLjava/util/Map;L expirationtLjava/util/Date;L refreshTokent?Lorg/springframework/security/oauth2/common/OAuth2RefreshToken;LscopetLjava/util/Set;L tokenTypetLjava/lang/String;Lvalueq~xpsrjava.util.Collections$EmptyMapY6�Z���xpsrjava.util.Datehj�KYtx`3�}xpsr%java.util.Collections$UnmodifiableSet��я��Uxr,java.util.Collections$UnmodifiableCollectionB��^�LctLjava/util/Collection;xpsrjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw ?@t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
這個key的命名結構是access:token
value的結構是string,存儲的是OAuth2AccessToken的序列化值spring-boot
spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStore.java加密
public class RedisTokenStore implements TokenStore { private static final String ACCESS = "access:"; private static final String AUTH_TO_ACCESS = "auth_to_access:"; private static final String AUTH = "auth:"; private static final String REFRESH_AUTH = "refresh_auth:"; private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; private static final String REFRESH = "refresh:"; private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:"; private static final String UNAME_TO_ACCESS = "uname_to_access:"; @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { byte[] serializedAccessToken = serialize(token); byte[] serializedAuth = serialize(authentication); byte[] accessKey = serializeKey(ACCESS + token.getValue()); byte[] authKey = serializeKey(AUTH + token.getValue()); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.set(accessKey, serializedAccessToken); conn.set(authKey, serializedAuth); conn.set(authToAccessKey, serializedAccessToken); if (!authentication.isClientOnly()) { conn.rPush(approvalKey, serializedAccessToken); } conn.rPush(clientId, serializedAccessToken); if (token.getExpiration() != null) { int seconds = token.getExpiresIn(); conn.expire(accessKey, seconds); conn.expire(authKey, seconds); conn.expire(authToAccessKey, seconds); conn.expire(clientId, seconds); conn.expire(approvalKey, seconds); } OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null && refreshToken.getValue() != null) { byte[] refresh = serialize(token.getRefreshToken().getValue()); byte[] auth = serialize(token.getValue()); byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue()); conn.set(refreshToAccessKey, auth); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue()); conn.set(accessToRefreshKey, refresh); if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; Date expiration = expiringRefreshToken.getExpiration(); if (expiration != null) { int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) .intValue(); conn.expire(refreshToAccessKey, seconds); conn.expire(accessToRefreshKey, seconds); } } } conn.closePipeline(); } finally { conn.close(); } } //...... }
spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/provider/token/DefaultAuthenticationKeyGenerator.java
public String extractKey(OAuth2Authentication authentication) { Map<String, String> values = new LinkedHashMap<String, String>(); OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (!authentication.isClientOnly()) { values.put(USERNAME, authentication.getName()); } values.put(CLIENT_ID, authorizationRequest.getClientId()); if (authorizationRequest.getScope() != null) { values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope()))); } return generateKey(values); } protected String generateKey(Map<String, String> values) { MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); byte[] bytes = digest.digest(values.toString().getBytes("UTF-8")); return String.format("%032x", new BigInteger(1, bytes)); } catch (NoSuchAlgorithmException nsae) { throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).", nsae); } catch (UnsupportedEncodingException uee) { throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).", uee); } }
使用redis存儲token能夠利用redis的過時時間來自動處理token的過時時間,而使用數據庫來存儲的話,則須要根據expired date來判斷。
可是redis不能像關係數據庫那樣直接關聯查詢,所以須要本身額外構造須要關聯的key來處理,具體使用須要屢次查詢。
排除refresh_token,主要key以下:
這個主要是經過OAuth2Authentication來獲取OAuth2AccessToken
這個主要用來獲取token的OAuth2Authentication,用來獲取相應的權限信息
這個主要是存儲了每一個clientId申請的OAuth2AccessToken的集合
方便用來審計和應急處理跟clientId相關的token
這個主要是經過token值來獲取OAuth2AccessToken
存儲OAuth2AccessToken的集合主要是爲了經過clientId,userId來獲取OAuth2AccessToken集合,方便用來獲取及revoke approval