sequence

Spring Security OAuth2 uses Redis to store token configuration and storage structure in Redis

maven

	<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>
Copy the code

configuration

@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 TokenStoretokenStore() {
        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(); }}Copy the code

The Redis Token Store is configured here

Of course, the configuration file needs to specify the redis address

spring.redis.url=redis://localhost:6379
Copy the code

Redis stores structures

keys *

root@d8bfc99e9e07:/data# redis-cli --raw127.0.0.1:6379 > keys * auth_to_access: 0 ec8e677177b8eff1dc1c5f97a56836f uname_to_access: demoApp: demoUser1 auth:8c1441db-6fac-4c57-9cc9-7c5adaafc18a client_id_to_access:demoApp access:8c1441db-6fac-4c57-9cc9-7c5adaafc18aCopy the code

You can see that there are five keys in there

auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f

127.0.0.1:6379 >typeAuth_to_access: 0 ec8e677177b8eff1dc1c5f97a56836f string 127.0.0.1:6379 > get Auth_to_access: 0 ec8e677177b8eff1dc1c5f97a56836f � � srCorg.springframework.security.oauth2.com mon. 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$EmptyMapY6Z � � � � xpsrjava. Util. Datehj � KYtx ` 3 �} XPSR % Java. The util. Collections$UnmodifiableSet� � second � � Uxr, Java. Util. Collections$UnmodifiableCollectionB� � ^ � LctLjava/util/Collection; Xpsrjava. Util. LinkedHashSet � � l Z � � * xrjava util. HashSet � D � � � � � 4 XPW? @t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
Copy the code

Is the key of naming structure auth_to_access: OAuth2Authentication information encrypted value, default is md5 encryption is OAuth2AccessToken serialized value of the specific storage

uname_to_access:demoApp:demoUser1

127.0.0.1:6379 >typeUname_to_access :demoApp:demoUser1 list 127.0.0.1:6379> lrange unAME_to_access :demoApp:demoUser1 0-1 Mon. � � srCorg.springframework.security.oauth2.com 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$EmptyMapY6Z � � � � xpsrjava. Util. Datehj � KYtx ` 3 �} XPSR % Java. The util. Collections$UnmodifiableSet� � second � � Uxr, Java. Util. Collections$UnmodifiableCollectionB� � ^ � LctLjava/util/Collection; Xpsrjava. Util. LinkedHashSet � � l Z � � * xrjava util. HashSet � D � � � � � 4 XPW? @t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
Copy the code

This key is named uname_to_access:clientId:userId. The value structure is a list and stores the serialized value of the token

auth:8c1441db-6fac-4c57-9cc9-7c5adaafc18a

127.0.0.1:6379 >typeAuth: 8C1441DB-6FA-4C57-9CC9-7C5ADAAFC18A string 127.0.0.1:6379> get auth: 8c1441db-6FA-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 � Lrol etLjava/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� � second � � Uxq ~ srjava. Util. LinkedHashSet � � l Z � � * xrjava util. HashSet � D � � � � � 4 XPW? @t read_contactsxsq~+w ? @xsq~? @xthttp://localhost:8081/callbackpsq~+w ? @xsq~+w ? @q~! XsrOorg. Springframework. Security. Authentication. UsernamePasswordAuthenticationToken credentialsq � L ~ L principalq~xq~sq~sq~ wq~xq~7srHorg.springframework.securiremoteAddressq~Lation.WesessionIdq~xpt0:0:0:0:0:0:0:1t 1 c57de920efb42eec0387d162d91b30apsr2org. Springframework. Security. Core. Populated userdetails. The User � ZaccountNonExpiredZaccountNonLock edZcredentialsNonExpiredZenabledL Authoritiesq passwordq ~ ~ usernameq ~ XPSQ ~ (srjava. Util. TreeSet ݘ P � � � [xpsrForg. Springframework. Security. Core. Populated userdetails. User$AuthorityComparator� XPWQ ~ XPT demoUser1Copy the code

This key is named as auth:token value. Value is a string and stores the serialized value of OAuth2Authentication

client_id_to_access:demoApp

127.0.0.1:6379 >typeClient_id_to_access :demoApp list 127.0.0.1:6379> lrange client_ID_to_access :demoApp 0-1 Mon. � � srCorg.springframework.security.oauth2.com 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$EmptyMapY6Z � � � � xpsrjava. Util. Datehj � KYtx ` 3 �} XPSR % Java. The util. Collections$UnmodifiableSet� � second � � Uxr, Java. Util. Collections$UnmodifiableCollectionB� � ^ � LctLjava/util/Collection; Xpsrjava. Util. LinkedHashSet � � l Z � � * xrjava util. HashSet � D � � � � � 4 XPW? @t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
Copy the code

The key naming structure is client_ID_to_access: the clientId value structure is the list that stores the serialized values of OAuth2AccessToken

access:8c1441db-6fac-4c57-9cc9-7c5adaafc18a

127.0.0.1:6379 >typeAccess: 8c1441db-6FA-4C57-9CC9-7C5ADAAFC18a string 127.0.0.1:6379> get access: 8c1441db-6FA-4c57-9CC9-7C5ADaafc18a Mon. � � srCorg.springframework.security.oauth2.com 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$EmptyMapY6Z � � � � xpsrjava. Util. Datehj � KYtx ` 3 �} XPSR % Java. The util. Collections$UnmodifiableSet� � second � � Uxr, Java. Util. Collections$UnmodifiableCollectionB� � ^ � LctLjava/util/Collection; Xpsrjava. Util. LinkedHashSet � � l Z � � * xrjava util. HashSet � D � � � � � 4 XPW? @t read_contactsxtbearert$8c1441db-6fac-4c57-9cc9-7c5adaafc18a
Copy the code

The token value is a string and stores the serialized value of OAuth2AccessToken

The source code

Spring ws-security – oauth2-2.0.14. RELEASE – sources. The 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(); }} / /... }Copy the code

Spring ws-security – oauth2-2.0.14. RELEASE – sources. The 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); }}Copy the code

summary

benefits

If you use Redis to store tokens, the expiration time of the tokens can be automatically handled. If you use a database to store tokens, you need to determine the expiration date according to the expired date.

disadvantages

However, Redis cannot directly associate query like relational database, so it needs to construct additional key to be associated to process, and the specific use requires multiple queries.

Excluding refresh_token, the main keys are as follows:

  • Auth_to_access: OAuth2Authentication information encrypted value, the value string structure

This is mainly OAuth2AccessToken via OAuth2Authentication

  • Auth: indicates the token value. Value is a string structure

This is mainly used to obtain the OAuth2Authentication of the token, used to obtain the corresponding permission information

  • Client_id_to_access :clientId, value is a list structure

This is a collection of OAuth2AccessToken that stores each clientId application for auditing and emergency handling of clientId related tokens

  • Access :token value. Value is string

OAuth2AccessToken is obtained through the token value

  • Uname_to_access: clientId: userId, structure is a list of the value

The OAuth2AccessToken set is stored mainly for access to the OAuth2AccessToken set via the clientId,userId, which can be used to obtain and revoke Approval