提交 d78ec64b 编写于 作者: zlt2000's avatar zlt2000

1.增加token自动续签功能(仅限于redis token模式),可配置化:开关、白名单、黑名单

2.项目默认token模式改为redis,并且开启自动续签功能
上级 f0c07434
......@@ -19,5 +19,10 @@ public class AuthProperties {
/**
* 是否开启url权限验证
*/
private boolean urlEnabled = false;
private Boolean urlEnabled = false;
/**
* token自动续签配置(目前只有redis实现)
*/
private RenewProperties renew = new RenewProperties();
}
package com.central.oauth2.common.properties;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* 续签配置
*
* @author zlt
* @date 2019/7/9
*/
@Setter
@Getter
public class RenewProperties {
/**
* 是否开启token自动续签(目前只有redis实现)
*/
private Boolean enable = false;
/**
* 白名单,配置需要自动续签的应用id(与黑名单互斥,只能配置其中一个),不配置默认所有应用都生效
* 配置enable为true时才生效
*/
private List<String> includeClientIds = new ArrayList<>();
/**
* 黑名单,配置不需要自动续签的应用id(与白名单互斥,只能配置其中一个)
* 配置enable为true时才生效
*/
private List<String> exclusiveClientIds = new ArrayList<>();
}
......@@ -49,7 +49,7 @@ public abstract class DefaultPermissionServiceImpl implements IPermissionService
}
if (!(authentication instanceof AnonymousAuthenticationToken)) {
//判断是否开启url权限验证
if (!securityProperties.getAuth().isUrlEnabled()) {
if (!securityProperties.getAuth().getUrlEnabled()) {
return true;
}
//超级管理员admin不需认证
......
package com.central.oauth2.common.store;
import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
......@@ -16,8 +18,11 @@ public class AuthRedisTokenStore {
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private SecurityProperties securityProperties;
@Bean
public TokenStore tokenStore() {
return new CustomRedisTokenStore(connectionFactory);
return new CustomRedisTokenStore(connectionFactory, securityProperties);
}
}
package com.central.oauth2.common.store;
import com.central.common.constant.SecurityConstants;
import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
......@@ -30,6 +35,7 @@ import java.util.List;
* 1. 支持redis cluster模式
* 2. 非cluster模式时使用pipeline减少连接次数
* 3. CLIENT_ID_TO_ACCESS集合改为list,方便业务顺序遍历
* 4. 自动续签token(可配置是否开启)
*
* @author zlt
* @date 2019/7/7
......@@ -44,6 +50,10 @@ public class CustomRedisTokenStore implements TokenStore {
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:";
/**
* 续签时间比例,当前剩余时间小于小于过期总时长的50%则续签
*/
private static final Double RENEW_RATIO = 0.5;
private static final boolean springDataRedis_2_0 = ClassUtils.isPresent(
"org.springframework.data.redis.connection.RedisStandaloneConfiguration",
......@@ -57,8 +67,14 @@ public class CustomRedisTokenStore implements TokenStore {
private Method redisConnectionSet_2_0;
public CustomRedisTokenStore(RedisConnectionFactory connectionFactory) {
/**
* 认证配置
*/
private SecurityProperties securityProperties;
public CustomRedisTokenStore(RedisConnectionFactory connectionFactory, SecurityProperties securityProperties) {
this.connectionFactory = connectionFactory;
this.securityProperties = securityProperties;
if (springDataRedis_2_0) {
this.loadRedisConnectionMethods_2_0();
}
......@@ -105,6 +121,10 @@ public class CustomRedisTokenStore implements TokenStore {
return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);
}
private ClientDetails deserializeClientDetails(byte[] bytes) {
return serializationStrategy.deserialize(bytes, ClientDetails.class);
}
private byte[] serialize(String string) {
return serializationStrategy.serialize(string);
}
......@@ -140,7 +160,68 @@ public class CustomRedisTokenStore implements TokenStore {
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
OAuth2Authentication auth2Authentication = readAuthentication(token.getValue());
//是否开启token续签
boolean isRenew = securityProperties.getAuth().getRenew().getEnable();
if (isRenew && auth2Authentication != null) {
OAuth2Request clientAuth = auth2Authentication.getOAuth2Request();
//判断当前应用是否需要自动续签
if (checkRenewClientId(clientAuth.getClientId())) {
//获取过期时长
int validitySeconds = getAccessTokenValiditySeconds(clientAuth.getClientId());
if (validitySeconds > 0) {
int expiresIn = token.getExpiresIn();
double expiresRatio = expiresIn / (double)validitySeconds;
//判断是否需要续签,当前剩余时间小于过期时长的50%则续签
if (expiresRatio <= RENEW_RATIO) {
//更新AccessToken过期时间
DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token;
oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
storeAccessToken(oAuth2AccessToken, auth2Authentication, true);
}
}
}
}
return auth2Authentication;
}
/**
* 判断应用自动续签是否满足白名单和黑名单的过滤逻辑
* @param clientId 应用id
* @return 是否满足
*/
private boolean checkRenewClientId(String clientId) {
boolean result = true;
//白名单
List<String> includeClientIds = securityProperties.getAuth().getRenew().getIncludeClientIds();
//黑名单
List<String> exclusiveClientIds = securityProperties.getAuth().getRenew().getExclusiveClientIds();
if (includeClientIds.size() > 0) {
result = includeClientIds.contains(clientId);
} else if(exclusiveClientIds.size() > 0) {
result = !exclusiveClientIds.contains(clientId);
}
return result;
}
/**
* 获取token的总有效时长
* @param clientId 应用id
*/
private int getAccessTokenValiditySeconds(String clientId) {
RedisConnection conn = getConnection();
byte[] bytes;
try {
bytes = conn.get(serializeKey(SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId));
} finally {
conn.close();
}
ClientDetails clientDetails = deserializeClientDetails(bytes);
if (clientDetails != null && clientDetails.getAccessTokenValiditySeconds() != null) {
return clientDetails.getAccessTokenValiditySeconds();
}
//返回默认值
return SecurityConstants.ACCESS_TOKEN_VALIDITY_SECONDS;
}
@Override
......@@ -152,8 +233,7 @@ public class CustomRedisTokenStore implements TokenStore {
} finally {
conn.close();
}
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
return deserializeAuthentication(bytes);
}
@Override
......@@ -165,8 +245,7 @@ public class CustomRedisTokenStore implements TokenStore {
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
return deserializeAuthentication(bytes);
} finally {
conn.close();
}
......@@ -174,6 +253,14 @@ public class CustomRedisTokenStore implements TokenStore {
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
storeAccessToken(token, authentication, false);
}
/**
* 存储token
* @param isExpire 是否刷新过期时间
*/
private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isExpire) {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue());
......@@ -198,10 +285,13 @@ public class CustomRedisTokenStore implements TokenStore {
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
}
if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, serializedAccessToken);
//如果是刷新token过期时间,不需要再往集合添加token
if (!isExpire) {
if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
......@@ -345,8 +435,7 @@ public class CustomRedisTokenStore implements TokenStore {
} finally {
conn.close();
}
OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes);
return refreshToken;
return deserializeRefreshToken(bytes);
}
@Override
......
......@@ -136,4 +136,8 @@ public interface SecurityConstants {
* 登出URL
*/
String LOGOUT_URL = "/oauth/remove/token";
/**
* 默认token过期时间(1小时)
*/
Integer ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60;
}
......@@ -27,7 +27,7 @@ CREATE TABLE `oauth_client_details` (
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES (1, 'app', NULL, '$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO', 'app', 'app', 'password,refresh_token', NULL, NULL, 18000, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (2, 'mobile', 'mobile,test', '$2a$10$ULxRssv/4NWOc388lZFbyus3IFfsbcpG/BZOq4TRxDhsx5HHIR7Jm', 'mobile', 'all', 'password,refresh_token', NULL, NULL, 18000, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (4, 'webApp', NULL, '$2a$10$06msMGYRH8nrm4iVnKFNKOoddB8wOwymVhbUzw/d3ZixD7Nq8ot72', 'webApp', 'app', 'authorization_code,password,refresh_token,client_credentials', NULL, NULL, 18000, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (11, 'zlt', NULL, '$2a$10$/o.wuORzVcXaezmYVzwYMuoY7qeWXBALwQmkskXD/7C6rqfCyPrna', 'zlt', 'all', 'authorization_code,password,refresh_token,client_credentials', 'http://127.0.0.1:8080/singleLogin', NULL, 18000, 28800, '{}', 'true', '2018-12-27 00:50:30', '2018-12-27 00:50:30');
\ No newline at end of file
INSERT INTO `oauth_client_details` VALUES (1, 'app', NULL, '$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO', 'app', 'app', 'password,refresh_token', NULL, NULL, 3600, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (2, 'mobile', 'mobile,test', '$2a$10$ULxRssv/4NWOc388lZFbyus3IFfsbcpG/BZOq4TRxDhsx5HHIR7Jm', 'mobile', 'all', 'password,refresh_token', NULL, NULL, 3600, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (4, 'webApp', NULL, '$2a$10$06msMGYRH8nrm4iVnKFNKOoddB8wOwymVhbUzw/d3ZixD7Nq8ot72', 'webApp', 'app', 'authorization_code,password,refresh_token,client_credentials', NULL, NULL, 3600, NULL, '{}', 'true', NULL, NULL);
INSERT INTO `oauth_client_details` VALUES (11, 'zlt', NULL, '$2a$10$/o.wuORzVcXaezmYVzwYMuoY7qeWXBALwQmkskXD/7C6rqfCyPrna', 'zlt', 'all', 'authorization_code,password,refresh_token,client_credentials', 'http://127.0.0.1:8080/singleLogin', NULL, 3600, 28800, '{}', 'true', '2018-12-27 00:50:30', '2018-12-27 00:50:30');
\ No newline at end of file
......@@ -29,7 +29,7 @@ zlt:
oauth2:
token:
store:
type: resJwt
type: redis
security:
ignore:
# 忽略认证的地址
......@@ -45,6 +45,13 @@ zlt:
httpUrls: /api-uaa/clients/**
#是否开启url级别权限
urlEnabled: false
renew:
#是否开启token自动续签(目前只有redis实现)
enable: true
#白名单
includeClientIds:
- webApp
zuul:
ribbon-isolation-strategy: thread
......
......@@ -2,10 +2,8 @@ package com.central.oauth.controller;
import com.central.common.constant.SecurityConstants;
import com.central.common.model.Result;
import com.central.common.utils.SpringUtil;
import com.central.oauth.mobile.MobileAuthenticationToken;
import com.central.oauth.openid.OpenIdAuthenticationToken;
import com.central.oauth.service.impl.RedisClientDetailsService;
import com.central.oauth2.common.util.AuthUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
......@@ -22,10 +20,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -57,6 +52,9 @@ public class OAuth2Controller {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsService clientDetailsService;
@ApiOperation(value = "用户名密码获取token")
@PostMapping(SecurityConstants.PASSWORD_LOGIN_PRO_URL)
public void getUserTokenInfo(
......@@ -93,7 +91,7 @@ public class OAuth2Controller {
String clientId = clientInfos[0];
String clientSecret = clientInfos[1];
ClientDetails clientDetails = getClient(clientId, clientSecret, null);
ClientDetails clientDetails = getClient(clientId, clientSecret);
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "customer");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
Authentication authentication = authenticationManager.authenticate(token);
......@@ -129,10 +127,7 @@ public class OAuth2Controller {
}
}
private ClientDetails getClient(String clientId, String clientSecret, RedisClientDetailsService clientDetailsService) {
if (clientDetailsService == null) {
clientDetailsService = SpringUtil.getBean(RedisClientDetailsService.class);
}
private ClientDetails getClient(String clientId, String clientSecret) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
......
......@@ -17,7 +17,7 @@ zlt:
oauth2:
token:
store:
type: authJwt
type: redis
swagger:
enabled: true
title: 认证中心
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册