Spring Security Oauth2 之RedisTokenStore

[本文仅仅作为自我学习的笔记,有什么不对或误人之处,望大佬指正]

Spring Security Oauth2 之RedisTokenStore

Token存储方式

Spring Security Oauth2 存储Token的方式有多种, 比如JWT、Jdbc(数据库)、Redis等,根据Oauth2继承类图,实现方式如下:
TokenStrore实现方式
使用Redis存储Token具有明显的优势,我自己开发学习的过程使用RedisTokenStore。

Redis key存储内容

在使用Redis存储token,spring security oauth2 会生成以下几个key, 直接放出RedisTokenStore的源码吧:

	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:";

这几个存储的key都存了什么内容,有什么含义呢?
于是我打开redis-cli,迫不及待的看一下这个key “shield:oauth:access:x8U6xmAK0MeFDEJ0”(“shield:oauth:”是项目自定义的前缀)shield:oauth:access:x8U6xmAK0MeFDEJ0
尼玛,这是什么东西?我们来看一下RedisTokenStore的源码:

//spring secuity oauth2提供的一个序列化工具
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
//存储OAuth2AccessToken
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
		byte[] serializedAccessToken = serialize(token);
		......
}
//把存储对象序列化
private byte[] serialize(Object object) {
	return serializationStrategy.serialize(object);
}

通过源码,我们发现spring security oauth2存储的是序列化后的对象,而不是json。(注意:应该是为提高存储效率,而不是加密操作)
那我们就以access:[AccessToken]这个key为例,在自己项目里写一个controller看看这个对象内容(自己debug也行,junit单元测试也行,自己能看到内容就好)

    @ApiOperation(value = "获取access存储内容", httpMethod = "GET")
    @GetMapping("/deserialize/access")
    public R<Object> deserializeAccessToken(
        @ApiParam("accessToken") @NotBlank(message = "accessToken不能为空") @RequestParam("accessToken") String accessToken
    ) {
        RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
        byte[] serializedKey = serializationStrategy.serialize(Prefix.REDIS_SHIELD_OAUTH2 + "access:" + accessToken);
        RedisConnection conn = connectionFactory.getConnection();
        byte[] bytes;
        try {
            bytes = conn.get(serializedKey);
        } finally {
            conn.close();
        }
        OAuth2AccessToken content = serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);
        return RUtil.success(content);
    }

经过这个controller,还原成我们常用的json格式,如下:

{
    "access_token": "x8U6xmAK0MeFDEJ0",
    "token_type": "bearer",
    "refresh_token": "0qLDRZE70MeFDEI!",
    "expires_in": 29658,
    "scope": "server"
  }

好了,既然我们key的内容知道了,我们就逐一分析一下这几个key的用处。

1. access:[AccessToken](对应对象:OAuth2AccessToken)

要如何获取这个key? 如果你已有spring security oauth2的项目(如果没有,可以github或spring官网找一个sample项目或自己搭建一个),可以类似这样发送一个请求:

http://localhost:6799/oauth/token?grant_type=password&username=wuji&password=12345678&client_id=app&client_secret=app

spring security oauth2获取AccessToken如果是密码授权方式(grant_type=password)除了携带用户名以及密码还要携带client_id和client_secret,当然,你也可以Http Basic方式将client_id和client_secret到请求头,如下:HTTP Basic
YXBwOmFwcA==是“client_id:client_secret” base64编码后的结果。
好了,我们试着发送一个请求,返回结果如下:

{
	//token,拿着这个token我们就可以资源(接口)了
    "access_token": "x8U6xmAK0MeFDEJ0",
    //token类型是一个票据类型,还有授权码类型等等
    "token_type": "bearer",
    //用与刷新access_token(资源访问token)的token
    "refresh_token": "0qLDRZE70MeFDEI!",
    //access_token剩余存活时间(单位是秒)
    "expires_in": 29658,
    //拿这个token可以访问那些范围内的资源
    "scope": "server"
  }
2. auth_to_access:[这里不再是AccessToken]

通过阅读源码,发现:

public String extractKey(OAuth2Authentication authentication) {
	//省略一些代码
	values.put(USERNAME, authentication.getName());
	values.put(CLIENT_ID, authorizationRequest.getClientId());
	values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
	return generateKey(values);
}

auth_to_access:key, key是将username、client_id、scope三个值加密后的值,我们再看auth_to_access:存储的内容如下:

{
    "access_token": "x8U6xmAK0MeFDEJ0",
    "token_type": "bearer",
    "refresh_token": "0qLDRZE70MeFDEI!",
    "expires_in": 24126,
    "scope": "server"
  }

跟access:的内容一模一样,那我们就知道了,代码内部实现可以通过username、client_id、scope 3个字段获取AccessToken。

3.auth:[accessToken] (对应对象:OAuth2Authentication)

auth:存的内容如下:

{
    "authorities": [{
            "authority": "ROLE_USER"
        },
        {
            "authority": "USER_RETRIEVE"
        }
    ],
    "details": null,
    "authenticated": true,
    "userAuthentication": {
        "authorities": [{
                "authority": "ROLE_USER"
            },
            {
                "authority": "USER_RETRIEVE"
            }
        ],
        "details": {
            "client_secret": "app",
            "grant_type": "password",
            "client_id": "app",
            "username": "wuji"
        },
        "authenticated": true,
        "principal": {
            "password": null,
            "username": "wuji",
            "authorities": [{
                    "authority": "ROLE_USER"
                },
                {
                    "authority": "USER_RETRIEVE"
                }
            ],
            "accountNonExpired": true,
            "accountNonLocked": true,
            "credentialsNonExpired": true,
            "enabled": true
        },
        "credentials": null,
        "name": "wuji"
    },
    "credentials": "",
    "principal": {
        "password": null,
        "username": "wuji",
        "authorities": [{
                "authority": "ROLE_USER"
            },
            {
                "authority": "USER_RETRIEVE"
            }
        ],
        "accountNonExpired": true,
        "accountNonLocked": true,
        "credentialsNonExpired": true,
        "enabled": true
    },
    "oauth2Request": {
        "clientId": "app",
        "scope": ["server"],
        "requestParameters": {
            "grant_type": "password",
            "client_id": "app",
            "username": "wuji"
        },
        "resourceIds": [],
        "authorities": [],
        "approved": true,
        "refresh": false,
        "redirectUri": null,
        "responseTypes": [],
        "extensions": {},
        "grantType": "password",
        "refreshTokenRequest": null
    },
    "clientOnly": false,
    "name": "wuji"
}

主要包含当前登录用户的信息,以及用户附带的角色和和权限信息、生成Token时的授权方式等信息。

4.access_to_refresh:[accessToken]

access_to_refresh:存储的内容很简单,在通过password等授权方式获取token时的refreshToken

//refresh_token
grz0Xlzi0MeQwkx9
5.refresh_to_access:[refreshToken]

拿refreshToken去刷新accessToken时,会将新生成的accessToken放到refresh_to_access:

//access_token
TesxUOBt0MeRDDxA
6.refresh:[refreshToken]

存储内容如下:

{
 //refresh_toekn
  "value": "grz0Xlzi0MeQwkx9",
  //过期时间戳(mills)
  "expiration": 1557821765322
}

拿refreshToken去刷新accessToken时, 会先拿到这个KEY的信息,判断请求方的refresh token是否有效,无效的不能刷新access token。

7.refresh_auth:[refreshToken] (对应对象:OAuth2Authentication)

refresh_auth:存的内容与auth:类似,如下:

{
  "authorities": [
    {
      "authority": "ROLE_USER"
    },
    {
      "authority": "USER_RETRIEVE"
    }
  ],
  "details": null,
  "authenticated": true,
  "userAuthentication": {
    "authorities": [
      {
        "authority": "ROLE_USER"
      },
      {
        "authority": "USER_RETRIEVE"
      }
    ],
    "details": {
      "client_secret": "app",
      "grant_type": "password",
      "client_id": "app",
      "username": "wuji"
    },
    "authenticated": true,
    "principal": {
      "password": null,
      "username": "wuji",
      "authorities": [
        {
          "authority": "ROLE_USER"
        },
        {
          "authority": "USER_RETRIEVE"
        }
      ],
      "accountNonExpired": true,
      "accountNonLocked": true,
      "credentialsNonExpired": true,
      "enabled": true
    },
    "credentials": null,
    "name": "wuji"
  },
  "credentials": "",
  "principal": {
    "password": null,
    "username": "wuji",
    "authorities": [
      {
        "authority": "ROLE_USER"
      },
      {
        "authority": "USER_RETRIEVE"
      }
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "oauth2Request": {
    "clientId": "app",
    "scope": ["server"],
    "requestParameters": {
      "grant_type": "password",
      "client_id": "app",
      "username": "wuji"
    },
    "resourceIds": [],
    "authorities": [],
    "approved": true,
    "refresh": false,
    "redirectUri": null,
    "responseTypes": [],
    "extensions": {},
    "grantType": "password",
    "refreshTokenRequest": null
  },
  "clientOnly": false,
  "name": "wuji"
}
8.client_id_to_access:[client_id]

存储内容:

[{
    "access_token": "TesxUOBt0MeRDDxA",
    "token_type": "bearer",
    "refresh_token": "grz0Xlzi0MeQwkx9",
    "expires_in": 41714,
    "scope": "server"
}]

顾名思义,这个key将对应client_id的AccessToken对象存储了起来,因为不同的username、scope使用同一个client_id去请求获取token,所以这是一个list。

9.uname_to_access:[client_id:username]

存储内容:

[{
    "access_token": "TesxUOBt0MeRDDxA",
    "token_type": "bearer",
    "refresh_token": "grz0Xlzi0MeQwkx9",
    "expires_in": 41714,
    "scope": "server"
}]

这里也是一个list。

发布了17 篇原创文章 · 获赞 9 · 访问量 6526

猜你喜欢

转载自blog.csdn.net/liuyanglglg/article/details/89077855