oauth2 redis mysql_spring security oauth2使用redis存储token

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

maven

org.springframework.boot

spring-boot-starter-security

org.springframework.security.oauth

spring-security-oauth2

org.springframework.boot

spring-boot-starter-data-redis

配置

@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地址

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

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

可以看到这里存了5个key

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

这个key的命名结构是auth_to_access:OAuth2Authentication相关信息的加密值,默认是md5加密

具体存储的是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

这个key的命名是uname_to_access:clientId:userId

value的结构是list,存储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

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

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

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

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 values = new LinkedHashMap();

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(authorizationRequest.getScope())));

}

return generateKey(values);

}

protected String generateKey(Map 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值