前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring security oauth2使用redis存储token

spring security oauth2使用redis存储token

作者头像
code4it
发布2018-09-17 15:32:23
3.1K0
发布2018-09-17 15:32:23
举报
文章被收录于专栏:码匠的流水账码匠的流水账

本文就来讲述一下spring security oauth2使用redis来存储token的配置及在redis中的存储结构

maven

代码语言:javascript
复制
    <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>

配置

代码语言:javascript
复制
@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

当然配置文件需要指定redis地址

代码语言:javascript
复制
spring.redis.url=redis://localhost:6379

redis中存储结构

keys *

代码语言:javascript
复制
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

auth_to_access:0ec8e677177b8eff1dc1c5f97a56836f

代码语言:javascript
复制
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的序列化的值

uname_to_access:demoApp:demoUser1

代码语言:javascript
复制
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的序列化值

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

代码语言:javascript
复制
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的序列化值

client_id_to_access:demoApp

代码语言:javascript
复制
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的序列化值

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

代码语言:javascript
复制
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-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStore.java

代码语言:javascript
复制
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

代码语言:javascript
复制
    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如下:

  • auth_to_access:OAuth2Authentication相关信息加密后的值,value为string结构 这个主要是通过OAuth2Authentication来获取OAuth2AccessToken
  • auth:token值,value为string结构 这个主要用来获取token的OAuth2Authentication,用来获取相应的权限信息
  • client_id_to_access:clientId,value为list结构 这个主要是存储了每个clientId申请的OAuth2AccessToken的集合 方便用来审计和应急处理跟clientId相关的token
  • access:token值,value为string 这个主要是通过token值来获取OAuth2AccessToken
  • uname_to_access:clientId:userId,value的结构是list 存储OAuth2AccessToken的集合 主要是为了通过clientId,userId来获取OAuth2AccessToken集合,方便用来获取及revoke approval
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码匠的流水账 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • maven
  • 配置
  • redis中存储结构
    • 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
              • 源码
              • 小结
                • 好处
                  • 缺点
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档