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

增加Token管理模块(仅限于redis token模式):"认证管理-token管理"

上级 cc3d784d
...@@ -4,7 +4,6 @@ import com.central.oauth2.common.properties.SecurityProperties; ...@@ -4,7 +4,6 @@ import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.TokenStore;
/** /**
......
...@@ -34,7 +34,7 @@ import java.util.List; ...@@ -34,7 +34,7 @@ import java.util.List;
* 优化自Spring Security的RedisTokenStore * 优化自Spring Security的RedisTokenStore
* 1. 支持redis cluster模式 * 1. 支持redis cluster模式
* 2. 非cluster模式时使用pipeline减少连接次数 * 2. 非cluster模式时使用pipeline减少连接次数
* 3. CLIENT_ID_TO_ACCESS集合改为list,方便业务顺序遍历 * 3. CLIENT_ID_TO_ACCESS和UNAME_TO_ACCESS集合改为list,方便业务顺序遍历
* 4. 自动续签token(可配置是否开启) * 4. 自动续签token(可配置是否开启)
* *
* @author zlt * @author zlt
...@@ -43,13 +43,10 @@ import java.util.List; ...@@ -43,13 +43,10 @@ import java.util.List;
public class CustomRedisTokenStore implements TokenStore { public class CustomRedisTokenStore implements TokenStore {
private static final String ACCESS = "access:"; private static final String ACCESS = "access:";
private static final String AUTH_TO_ACCESS = "auth_to_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 REFRESH_AUTH = "refresh_auth:";
private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
private static final String REFRESH = "refresh:"; private static final String REFRESH = "refresh:";
private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; 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%则续签 * 续签时间比例,当前剩余时间小于小于过期总时长的50%则续签
*/ */
...@@ -231,7 +228,7 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -231,7 +228,7 @@ public class CustomRedisTokenStore implements TokenStore {
byte[] bytes; byte[] bytes;
RedisConnection conn = getConnection(); RedisConnection conn = getConnection();
try { try {
bytes = conn.get(serializeKey(AUTH + token)); bytes = conn.get(serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token));
} finally { } finally {
conn.close(); conn.close();
} }
...@@ -266,17 +263,17 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -266,17 +263,17 @@ public class CustomRedisTokenStore implements TokenStore {
byte[] serializedAccessToken = serialize(token); byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication); byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue()); byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(AUTH + token.getValue()); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
RedisConnection conn = getConnection(); RedisConnection conn = getConnection();
try { try {
byte[] oldAccessToken = null; byte[] oldAccessToken = conn.get(accessKey);
//续签的话需要查询旧的token,用于集合删除 //如果token已存在,并且不是续签的话直接返回
if (isRenew) { if (!isRenew && oldAccessToken != null) {
oldAccessToken = conn.get(accessKey); return;
} }
this.openPipeline(conn); this.openPipeline(conn);
...@@ -296,12 +293,12 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -296,12 +293,12 @@ public class CustomRedisTokenStore implements TokenStore {
//如果是续签token,需要先删除集合里旧的值 //如果是续签token,需要先删除集合里旧的值
if (oldAccessToken != null) { if (oldAccessToken != null) {
if (!authentication.isClientOnly()) { if (!authentication.isClientOnly()) {
conn.sRem(approvalKey, oldAccessToken); conn.lRem(approvalKey, 1, oldAccessToken);
} }
conn.lRem(clientId, 1, oldAccessToken); conn.lRem(clientId, 1, oldAccessToken);
} }
if (!authentication.isClientOnly()) { if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, serializedAccessToken); conn.rPush(approvalKey, serializedAccessToken);
} }
conn.rPush(clientId, serializedAccessToken); conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) { if (token.getExpiration() != null) {
...@@ -367,7 +364,7 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -367,7 +364,7 @@ public class CustomRedisTokenStore implements TokenStore {
public void removeAccessToken(String tokenValue) { public void removeAccessToken(String tokenValue) {
byte[] accessKey = serializeKey(ACCESS + tokenValue); byte[] accessKey = serializeKey(ACCESS + tokenValue);
byte[] authKey = serializeKey(AUTH + tokenValue); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
RedisConnection conn = getConnection(); RedisConnection conn = getConnection();
try { try {
...@@ -384,11 +381,11 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -384,11 +381,11 @@ public class CustomRedisTokenStore implements TokenStore {
if (authentication != null) { if (authentication != null) {
String key = authenticationKeyGenerator.extractKey(authentication); String key = authenticationKeyGenerator.extractKey(authentication);
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
this.openPipeline(conn); this.openPipeline(conn);
conn.del(authToAccessKey); conn.del(authToAccessKey);
conn.sRem(unameKey, access); conn.lRem(unameKey, 1, access);
conn.lRem(clientId, 1, access); conn.lRem(clientId, 1, access);
conn.del(serialize(ACCESS + key)); conn.del(serialize(ACCESS + key));
this.closePipeline(conn); this.closePipeline(conn);
...@@ -499,30 +496,20 @@ public class CustomRedisTokenStore implements TokenStore { ...@@ -499,30 +496,20 @@ public class CustomRedisTokenStore implements TokenStore {
@Override @Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) { public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(clientId, userName));
List<byte[]> byteList; List<byte[]> byteList;
RedisConnection conn = getConnection(); RedisConnection conn = getConnection();
try { try {
byteList = getByteLists(approvalKey, conn); byteList = conn.lRange(approvalKey, 0, -1);
} finally { } finally {
conn.close(); conn.close();
} }
return getTokenCollections(byteList); return getTokenCollections(byteList);
} }
private List<byte[]> getByteLists(byte[] approvalKey, RedisConnection conn) {
List<byte[]> byteList;
Long size = conn.sCard(approvalKey);
byteList = new ArrayList<>(size.intValue());
Cursor<byte[]> cursor = conn.sScan(approvalKey, ScanOptions.NONE);
while(cursor.hasNext()) {
byteList.add(cursor.next());
}
return byteList;
}
@Override @Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) { public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId); byte[] key = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + clientId);
List<byte[]> byteList; List<byte[]> byteList;
RedisConnection conn = getConnection(); RedisConnection conn = getConnection();
try { try {
......
package com.central.common.annotation;
import java.lang.annotation.*;
/**
* 请求的方法参数上添加该注解,则注入当前登录账号的应用id
* 例:public void test(@LoginClient String clientId) //注入webApp
*
* @author zlt
* @date 2018/7/24 16:44
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginClient {
}
package com.central.common.config; package com.central.common.config;
import com.central.common.feign.UserService; import com.central.common.feign.UserService;
import com.central.common.resolver.ClientArgumentResolver;
import com.central.common.resolver.TokenArgumentResolver; import com.central.common.resolver.TokenArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
...@@ -26,6 +27,9 @@ public class LoginArgResolverConfig implements WebMvcConfigurer { ...@@ -26,6 +27,9 @@ public class LoginArgResolverConfig implements WebMvcConfigurer {
*/ */
@Override @Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注入用户信息
argumentResolvers.add(new TokenArgumentResolver(userService)); argumentResolvers.add(new TokenArgumentResolver(userService));
//注入应用信息
argumentResolvers.add(new ClientArgumentResolver());
} }
} }
...@@ -21,6 +21,11 @@ public interface SecurityConstants { ...@@ -21,6 +21,11 @@ public interface SecurityConstants {
*/ */
String ROLE_HEADER = "x-role-header"; String ROLE_HEADER = "x-role-header";
/**
* 应用信息头
*/
String CLIENT_HEADER = "x-client-header";
/** /**
* 基础角色 * 基础角色
*/ */
...@@ -140,4 +145,16 @@ public interface SecurityConstants { ...@@ -140,4 +145,16 @@ public interface SecurityConstants {
* 默认token过期时间(1小时) * 默认token过期时间(1小时)
*/ */
Integer ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60; Integer ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60;
/**
* redis中授权token对应的key
*/
String REDIS_TOKEN_AUTH = "auth:";
/**
* redis中应用对应的token集合的key
*/
String REDIS_CLIENT_ID_TO_ACCESS = "client_id_to_access:";
/**
* redis中用户名对应的token集合的key
*/
String REDIS_UNAME_TO_ACCESS = "uname_to_access:";
} }
package com.central.common.resolver;
import cn.hutool.core.util.StrUtil;
import com.central.common.annotation.LoginClient;
import com.central.common.constant.SecurityConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
/**
* head中的应用参数注入clientId中
*
* @author zlt
* @date 2019/7/10
*/
@Slf4j
public class ClientArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 入参筛选
*
* @param methodParameter 参数集合
* @return 格式化后的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginClient.class) && methodParameter.getParameterType().equals(String.class);
}
/**
* @param methodParameter 入参集合
* @param modelAndViewContainer model 和 view
* @param nativeWebRequest web相关
* @param webDataBinderFactory 入参解析
* @return 包装对象
*/
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String clientId = request.getHeader(SecurityConstants.CLIENT_HEADER);
if (StrUtil.isBlank(clientId)) {
log.warn("resolveArgument error clientId is empty");
}
return clientId;
}
}
...@@ -121,7 +121,7 @@ INSERT INTO `sys_menu` VALUES (9, 37, '文件中心', '#!files', 'files/files.ht ...@@ -121,7 +121,7 @@ INSERT INTO `sys_menu` VALUES (9, 37, '文件中心', '#!files', 'files/files.ht
INSERT INTO `sys_menu` VALUES (10, 37, '文档中心', '#!swagger', 'http://127.0.0.1:9900/swagger-ui.html', NULL, 'layui-icon-app', 4, '2017-11-17 16:56:59', '2019-01-17 20:18:48', 1, 0); INSERT INTO `sys_menu` VALUES (10, 37, '文档中心', '#!swagger', 'http://127.0.0.1:9900/swagger-ui.html', NULL, 'layui-icon-app', 4, '2017-11-17 16:56:59', '2019-01-17 20:18:48', 1, 0);
INSERT INTO `sys_menu` VALUES (11, 12, '我的信息', '#!myInfo', 'system/myInfo.html', NULL, '', 10, '2017-11-17 16:56:59', '2018-09-02 06:12:24', 1, 1); INSERT INTO `sys_menu` VALUES (11, 12, '我的信息', '#!myInfo', 'system/myInfo.html', NULL, '', 10, '2017-11-17 16:56:59', '2018-09-02 06:12:24', 1, 1);
INSERT INTO `sys_menu` VALUES (12, -1, '认证管理', 'javascript:;', '', NULL, 'layui-icon-set', 1, '2017-11-17 16:56:59', '2018-12-13 15:02:49', 1, 0); INSERT INTO `sys_menu` VALUES (12, -1, '认证管理', 'javascript:;', '', NULL, 'layui-icon-set', 1, '2017-11-17 16:56:59', '2018-12-13 15:02:49', 1, 0);
INSERT INTO `sys_menu` VALUES (35, 12, '应用管理', '#!app', 'attestation/app.html', NULL, 'layui-icon-link', 9, '2017-11-17 16:56:59', '2019-01-14 15:35:15', 1, 0); INSERT INTO `sys_menu` VALUES (35, 12, '应用管理', '#!app', 'attestation/app.html', NULL, 'layui-icon-link', 5, '2017-11-17 16:56:59', '2019-01-14 15:35:15', 1, 0);
INSERT INTO `sys_menu` VALUES (37, -1, '系统管理', 'javascript:;', '', NULL, 'layui-icon-set', 2, '2018-08-25 10:41:58', '2019-01-23 14:01:58', 1, 0); INSERT INTO `sys_menu` VALUES (37, -1, '系统管理', 'javascript:;', '', NULL, 'layui-icon-set', 2, '2018-08-25 10:41:58', '2019-01-23 14:01:58', 1, 0);
INSERT INTO `sys_menu` VALUES (62, 63, '应用监控', '#!admin', 'http://127.0.0.1:6500/#/wallboard', NULL, 'layui-icon-chart-screen', 3, '2019-01-08 15:32:19', '2019-01-17 20:22:44', 1, 0); INSERT INTO `sys_menu` VALUES (62, 63, '应用监控', '#!admin', 'http://127.0.0.1:6500/#/wallboard', NULL, 'layui-icon-chart-screen', 3, '2019-01-08 15:32:19', '2019-01-17 20:22:44', 1, 0);
INSERT INTO `sys_menu` VALUES (63, -1, '系统监控', 'javascript:;', '', NULL, 'layui-icon-set', 2, '2019-01-10 18:35:05', '2019-01-10 18:35:05', 1, 0); INSERT INTO `sys_menu` VALUES (63, -1, '系统监控', 'javascript:;', '', NULL, 'layui-icon-set', 2, '2019-01-10 18:35:05', '2019-01-10 18:35:05', 1, 0);
...@@ -135,6 +135,7 @@ INSERT INTO `sys_menu` VALUES (70, 63, 'APM监控', '#!apm', 'http://127.0.0.1:8 ...@@ -135,6 +135,7 @@ INSERT INTO `sys_menu` VALUES (70, 63, 'APM监控', '#!apm', 'http://127.0.0.1:8
INSERT INTO `sys_menu` VALUES (71, -1, '搜索管理', 'javascript:;', '', NULL, 'layui-icon-set', 3, '2018-08-25 10:41:58', '2019-01-23 15:07:07', 1, 0); INSERT INTO `sys_menu` VALUES (71, -1, '搜索管理', 'javascript:;', '', NULL, 'layui-icon-set', 3, '2018-08-25 10:41:58', '2019-01-23 15:07:07', 1, 0);
INSERT INTO `sys_menu` VALUES (72, 71, '索引管理', '#!index', 'search/index_manager.html', NULL, 'layui-icon-template', 1, '2019-01-10 18:35:55', '2019-01-12 00:27:20', 1, 0); INSERT INTO `sys_menu` VALUES (72, 71, '索引管理', '#!index', 'search/index_manager.html', NULL, 'layui-icon-template', 1, '2019-01-10 18:35:55', '2019-01-12 00:27:20', 1, 0);
INSERT INTO `sys_menu` VALUES (73, 71, '用户搜索', '#!userSearch', 'search/user_search.html', NULL, 'layui-icon-user', 2, '2019-01-10 18:35:55', '2019-01-12 00:27:20', 1, 0); INSERT INTO `sys_menu` VALUES (73, 71, '用户搜索', '#!userSearch', 'search/user_search.html', NULL, 'layui-icon-user', 2, '2019-01-10 18:35:55', '2019-01-12 00:27:20', 1, 0);
INSERT INTO `sys_menu` VALUES (74, 12, 'token管理', '#!tokens', 'system/tokens.html', NULL, 'layui-icon-unlink', 6, '2019-07-11 16:56:59', '2019-07-11 16:56:59', 1, 0);
-- ---------------------------- -- ----------------------------
-- Table structure for sys_role_menu -- Table structure for sys_role_menu
...@@ -170,6 +171,7 @@ INSERT INTO `sys_role_menu` VALUES (1, 70); ...@@ -170,6 +171,7 @@ INSERT INTO `sys_role_menu` VALUES (1, 70);
INSERT INTO `sys_role_menu` VALUES (1, 71); INSERT INTO `sys_role_menu` VALUES (1, 71);
INSERT INTO `sys_role_menu` VALUES (1, 72); INSERT INTO `sys_role_menu` VALUES (1, 72);
INSERT INTO `sys_role_menu` VALUES (1, 73); INSERT INTO `sys_role_menu` VALUES (1, 73);
INSERT INTO `sys_role_menu` VALUES (1, 74);
INSERT INTO `sys_role_menu` VALUES (2, 2); INSERT INTO `sys_role_menu` VALUES (2, 2);
INSERT INTO `sys_role_menu` VALUES (2, 3); INSERT INTO `sys_role_menu` VALUES (2, 3);
INSERT INTO `sys_role_menu` VALUES (2, 4); INSERT INTO `sys_role_menu` VALUES (2, 4);
......
...@@ -2,13 +2,13 @@ package com.central.gateway.filter.pre; ...@@ -2,13 +2,13 @@ package com.central.gateway.filter.pre;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import com.central.common.constant.SecurityConstants; import com.central.common.constant.SecurityConstants;
import com.central.common.model.SysUser;
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
...@@ -40,17 +40,14 @@ public class UserInfoHeaderFilter extends ZuulFilter { ...@@ -40,17 +40,14 @@ public class UserInfoHeaderFilter extends ZuulFilter {
public Object run() { public Object run() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) { if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
Object principal = authentication.getPrincipal(); String username = authentication.getName();
String userInfo;
if (principal instanceof SysUser) { OAuth2Authentication oauth2Authentication = (OAuth2Authentication)authentication;
SysUser user = (SysUser) principal; String clientId = oauth2Authentication.getOAuth2Request().getClientId();
userInfo = user.getUsername();
} else {
//jwt的token只有name
userInfo = authentication.getName();
}
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader(SecurityConstants.USER_HEADER, userInfo); ctx.addZuulRequestHeader(SecurityConstants.USER_HEADER, username);
ctx.addZuulRequestHeader(SecurityConstants.CLIENT_HEADER, clientId);
ctx.addZuulRequestHeader(SecurityConstants.ROLE_HEADER, CollectionUtil.join(authentication.getAuthorities(), ",")); ctx.addZuulRequestHeader(SecurityConstants.ROLE_HEADER, CollectionUtil.join(authentication.getAuthorities(), ","));
} }
return null; return null;
......
...@@ -42,7 +42,9 @@ zlt: ...@@ -42,7 +42,9 @@ zlt:
menusPaths: /api-user/menus/current menusPaths: /api-user/menus/current
auth: auth:
# 配置必需认证的url # 配置必需认证的url
httpUrls: /api-uaa/clients/** httpUrls: >
/api-uaa/clients/**,
/api-uaa/tokens/**
#是否开启url级别权限 #是否开启url级别权限
urlEnabled: false urlEnabled: false
renew: renew:
......
package com.central.oauth.controller;
import com.central.common.annotation.LoginClient;
import com.central.common.model.PageResult;
import com.central.oauth.model.TokenVo;
import com.central.oauth.service.ITokensService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* token管理接口
*
* @author zlt
*/
@Api(tags = "Token管理")
@RestController
@RequestMapping("/tokens")
public class TokensController {
@Autowired
private ITokensService tokensService;
@GetMapping("/list")
@ApiOperation(value = "token列表")
public PageResult<TokenVo> list(@RequestParam Map<String, Object> params, @LoginClient String clientId) {
return tokensService.listTokens(params, clientId);
}
}
package com.central.oauth.model;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* @author zlt
*/
@Setter
@Getter
public class TokenVo implements Serializable {
private static final long serialVersionUID = -6656955957477645319L;
/**
* token的值
*/
private String tokenValue;
/**
* 到期时间
*/
private Date expiration;
/**
* 用户名
*/
private String username;
/**
* 所属应用
*/
private String clientId;
}
package com.central.oauth.service;
import com.central.common.model.PageResult;
import com.central.oauth.model.TokenVo;
import java.util.Map;
/**
* @author zlt
*/
public interface ITokensService {
/**
* 查询token列表
* @param params 请求参数
* @param clientId 应用id
*/
PageResult<TokenVo> listTokens(Map<String, Object> params, String clientId);
}
package com.central.oauth.service.impl;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import com.central.common.constant.SecurityConstants;
import com.central.common.model.PageResult;
import com.central.common.redis.template.RedisRepository;
import com.central.oauth.model.TokenVo;
import com.central.oauth.service.ITokensService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* token管理服务(redis token)
*
* @author zlt
* @date 2019/7/12
*/
@Slf4j
@Service
public class RedisTokensServiceImpl implements ITokensService {
@Autowired
private RedisRepository redisRepository;
@Override
public PageResult<TokenVo> listTokens(Map<String, Object> params, String clientId) {
Integer page = MapUtils.getInteger(params, "page");
Integer limit = MapUtils.getInteger(params, "limit");
int[] startEnds = PageUtil.transToStartEnd(page, limit);
//根据请求参数生成redis的key
String redisKey = getRedisKey(params, clientId);
long size = redisRepository.length(redisKey);
List<TokenVo> result = new ArrayList<>(limit);
//查询token集合
List<Object> tokenObjs = redisRepository.getList(redisKey, startEnds[0], startEnds[1]-1);
if (tokenObjs != null) {
for (Object obj : tokenObjs) {
DefaultOAuth2AccessToken accessToken = (DefaultOAuth2AccessToken)obj;
//获取用户信息
Object authObj = redisRepository.get(SecurityConstants.REDIS_TOKEN_AUTH + accessToken.getValue());
OAuth2Authentication authentication = (OAuth2Authentication)authObj;
//构造token对象
TokenVo tokenVo = new TokenVo();
tokenVo.setClientId(clientId);
tokenVo.setTokenValue(accessToken.getValue());
tokenVo.setExpiration(accessToken.getExpiration());
tokenVo.setUsername(authentication.getName());
result.add(tokenVo);
}
}
return PageResult.<TokenVo>builder().data(result).code(0).count(size).build();
}
/**
* 根据请求参数生成redis的key
*/
private String getRedisKey(Map<String, Object> params, String clientId) {
String result;
String username = MapUtils.getString(params, "username");
if (StrUtil.isNotEmpty(username)) {
result = SecurityConstants.REDIS_UNAME_TO_ACCESS + clientId + ":" + username;
} else {
result = SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + clientId;
}
return result;
}
}
...@@ -29,8 +29,8 @@ zlt: ...@@ -29,8 +29,8 @@ zlt:
# 忽略认证的地址 # 忽略认证的地址
httpUrls: > httpUrls: >
/validata/**, /validata/**,
/clients,
/clients/**, /clients/**,
/tokens/**,
/login.html, /login.html,
/css/**, /css/**,
/images/**, /images/**,
......
...@@ -84,7 +84,7 @@ layui.define(['config', 'layer'], function (exports) { ...@@ -84,7 +84,7 @@ layui.define(['config', 'layer'], function (exports) {
return layer.open(param); return layer.open(param);
}, },
// 封装ajax请求,返回数据类型为json // 封装ajax请求,返回数据类型为json
req: function (url, data, success, method) { req: function (url, data, success, method, noHeaderToken) {
if ('put' == method.toLowerCase()) { if ('put' == method.toLowerCase()) {
method = 'PUT'; method = 'PUT';
} else if ('delete' == method.toLowerCase()) { } else if ('delete' == method.toLowerCase()) {
...@@ -99,9 +99,11 @@ layui.define(['config', 'layer'], function (exports) { ...@@ -99,9 +99,11 @@ layui.define(['config', 'layer'], function (exports) {
contentType: "application/json", contentType: "application/json",
success: success, success: success,
beforeSend: function (xhr) { beforeSend: function (xhr) {
var token = config.getToken(); if (!noHeaderToken) {
if (token) { let token = config.getToken();
xhr.setRequestHeader('Authorization', 'bearer ' + token.access_token); if (token) {
xhr.setRequestHeader('Authorization', 'bearer ' + token.access_token);
}
} }
} }
}); });
......
...@@ -217,7 +217,7 @@ layui.define(['config', 'admin', 'layer', 'laytpl', 'element', 'form'], function ...@@ -217,7 +217,7 @@ layui.define(['config', 'admin', 'layer', 'laytpl', 'element', 'form'], function
$('#btnLogout').click(function () { $('#btnLogout').click(function () {
layer.confirm('确定退出登录?', function () { layer.confirm('确定退出登录?', function () {
//通过认证中心 tuic //通过认证中心 tuic
admin.req('api-uaa/oauth/remove/token?access_token='+config.getToken().access_token, {}, function (data) { admin.req('api-uaa/oauth/remove/token', {}, function (data) {
config.removeToken(); config.removeToken();
location.replace('login.html'); location.replace('login.html');
}, 'POST'); }, 'POST');
......
<div class="layui-card">
<div class="layui-card-header">
<h2 class="header-title">token管理</h2>
<span class="layui-breadcrumb pull-right">
<a href="#!home_console">首页</a>
<a><cite>token管理</cite></a>
</span>
</div>
<div class="layui-card-body">
<div class="layui-form toolbar">
搜索:<input id="tokens-edit-search" class="layui-input search-input" type="text" placeholder="输入用户名"/>&emsp;
<button id="tokens-btn-search" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索</button>
</div>
<!-- 数据表格 -->
<table class="layui-table" id="tokens-table" lay-filter="tokens-table"></table>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="tokens-table-bar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<script>
layui.use(['table', 'util', 'config', 'admin'],function () {
let table = layui.table;
let config = layui.config;
let layer = layui.layer;
let util = layui.util;
let admin = layui.admin;
// 渲染表格
table.render({
elem: '#tokens-table',
url: config.base_server + 'api-uaa/tokens/list',
method: 'GET',
headers:{'Authorization': 'bearer ' + config.getToken().access_token},
page: true,
cols: [[
{type: 'numbers'},
{field: 'tokenValue',width:300, sort: true, title: 'token'},
{
sort: true, templet: function (d) {
return util.toDateString(d.expiration);
}, title: '到期时间'
},
{field: 'username',width:180, sort: true, title: '用户名'},
{field: 'clientId',width:180, sort: true, title: '所属应用'},
{align: 'center',width:100, toolbar: '#tokens-table-bar', title: '操作'}
]]
});
// 工具条点击事件
table.on('tool(tokens-table)', function (obj) {
if (obj.event === 'del') { // 删除
doDelete(obj);
}
});
// 删除
let doDelete = function (obj) {
layer.confirm('确定要删除吗?', function (i) {
layer.close(i);
layer.load(2);
admin.req('api-uaa/oauth/remove/token?access_token=' + obj.data.tokenValue, {}, function (data) {
layer.closeAll('loading');
layer.msg('成功', {icon: 1, time: 500});
obj.del();
}, 'DELETE', true);
});
};
// 搜索按钮点击事件
$('#tokens-btn-search').click(function () {
var key = $('#tokens-edit-search').val();
table.reload('tokens-table', {where: {username: key}});
});
});
</script>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册