Spring securityouth2 uses redis to store token

  spring-security

Order

This article will talk about the configuration of spring security oauth2 using redis to store token and the 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>

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 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 store is configured here.

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

spring.redis.url=redis://localhost:6379

Storage structure in redis

keys *

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

You can see that there are 5 keys stored here.

auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f

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

The naming structure of this key is auth _ to _ access: oauth2 authentication related information encryption value, which defaults to md5 encryption
Specifically stored is the serialized value of OAuth2AccessToken

uname_to_access:demoApp:demoUser1

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

The name of this key is uname _ to _ access: clientid: userid
The structure of value is list, which stores the serialized value of token.

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

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

The naming structure of this key is auth:token value
The structure of value is string, which stores the serialized value of OAuth2Authentication.

client_id_to_access:demoApp

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

The key naming structure is client_id_to_access:clientId
The value structure is a list that stores the serialized value of OAuth2AccessToken.

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

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

The naming structure of this key is access:token
The structure of value is string, which stores the serialized value of OAuth2AccessToken.

Source code

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);
        }
    }

Summary

Benefits

Using redis to store tokens can automatically process the expiration time of tokens by using the expiration time of redis, while using databases to store tokens needs to be judged by expired date.

Disadvantages

However, redis cannot directly associate queries like relational databases, so it needs to construct its own key to deal with it. Specific use requires multiple queries.

Excluding refresh_token, the main key is as follows:

  • Auth _ to _ access: the encrypted value of oauth2 authentication related information, and the value is a string structure

This is mainly through OAuth2Authentication to obtain OAuth2AccessToken.

  • Auth:token value, value is string structure

This is mainly used to obtain token OAuth2Authentication and to obtain corresponding permission information.

  • Client_id_to_access:clientId, value is list structure

This is mainly a collection that stores OAuth2AccessToken applied by each clientId.
Convenient for auditing and emergency handling of clientId-related token

  • Access:token value, value is string

This is mainly through the token value to obtain OAuth2AccessToken

  • Uname _ to _ access: clientid: userid, the structure of value is list

A collection that stores OAuth2AccessToken.
The main purpose is to obtain OAuth2AccessToken collection through clientId,userId, which is convenient for obtaining and revoking approval.