[本文仅仅作为自我学习的笔记,有什么不对或误人之处,望大佬指正]
Spring Security Oauth2 存储Token的方式有多种, 比如JWT、Jdbc(数据库)、Redis等,根据Oauth2继承类图,实现方式如下:
使用Redis存储Token具有明显的优势,我自己开发学习的过程使用RedisTokenStore。
在使用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:”是项目自定义的前缀)
尼玛,这是什么东西?我们来看一下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的用处。
要如何获取这个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到请求头,如下:
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"
}
通过阅读源码,发现:
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。
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时的授权方式等信息。
access_to_refresh:存储的内容很简单,在通过password等授权方式获取token时的refreshToken
//refresh_token
grz0Xlzi0MeQwkx9
拿refreshToken去刷新accessToken时,会将新生成的accessToken放到refresh_to_access:
//access_token
TesxUOBt0MeRDDxA
存储内容如下:
{
//refresh_toekn
"value": "grz0Xlzi0MeQwkx9",
//过期时间戳(mills)
"expiration": 1557821765322
}
拿refreshToken去刷新accessToken时, 会先拿到这个KEY的信息,判断请求方的refresh token是否有效,无效的不能刷新access token。
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"
}
存储内容:
[{
"access_token": "TesxUOBt0MeRDDxA",
"token_type": "bearer",
"refresh_token": "grz0Xlzi0MeQwkx9",
"expires_in": 41714,
"scope": "server"
}]
顾名思义,这个key将对应client_id的AccessToken对象存储了起来,因为不同的username、scope使用同一个client_id去请求获取token,所以这是一个list。
存储内容:
[{
"access_token": "TesxUOBt0MeRDDxA",
"token_type": "bearer",
"refresh_token": "grz0Xlzi0MeQwkx9",
"expires_in": 41714,
"scope": "server"
}]
这里也是一个list。