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

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

上级 cc3d784d
......@@ -4,7 +4,6 @@ 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;
/**
......
......@@ -34,7 +34,7 @@ import java.util.List;
* 优化自Spring Security的RedisTokenStore
* 1. 支持redis cluster模式
* 2. 非cluster模式时使用pipeline减少连接次数
* 3. CLIENT_ID_TO_ACCESS集合改为list,方便业务顺序遍历
* 3. CLIENT_ID_TO_ACCESS和UNAME_TO_ACCESS集合改为list,方便业务顺序遍历
* 4. 自动续签token(可配置是否开启)
*
* @author zlt
......@@ -43,13 +43,10 @@ import java.util.List;
public class CustomRedisTokenStore 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:";
/**
* 续签时间比例,当前剩余时间小于小于过期总时长的50%则续签
*/
......@@ -231,7 +228,7 @@ public class CustomRedisTokenStore implements TokenStore {
byte[] bytes;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializeKey(AUTH + token));
bytes = conn.get(serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token));
} finally {
conn.close();
}
......@@ -266,17 +263,17 @@ public class CustomRedisTokenStore implements TokenStore {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
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[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
RedisConnection conn = getConnection();
try {
byte[] oldAccessToken = null;
//续签的话需要查询旧的token,用于集合删除
if (isRenew) {
oldAccessToken = conn.get(accessKey);
byte[] oldAccessToken = conn.get(accessKey);
//如果token已存在,并且不是续签的话直接返回
if (!isRenew && oldAccessToken != null) {
return;
}
this.openPipeline(conn);
......@@ -296,12 +293,12 @@ public class CustomRedisTokenStore implements TokenStore {
//如果是续签token,需要先删除集合里旧的值
if (oldAccessToken != null) {
if (!authentication.isClientOnly()) {
conn.sRem(approvalKey, oldAccessToken);
conn.lRem(approvalKey, 1, oldAccessToken);
}
conn.lRem(clientId, 1, oldAccessToken);
}
if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, serializedAccessToken);
conn.rPush(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
......@@ -367,7 +364,7 @@ public class CustomRedisTokenStore implements TokenStore {
public void removeAccessToken(String 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);
RedisConnection conn = getConnection();
try {
......@@ -384,11 +381,11 @@ public class CustomRedisTokenStore implements TokenStore {
if (authentication != null) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
this.openPipeline(conn);
conn.del(authToAccessKey);
conn.sRem(unameKey, access);
conn.lRem(unameKey, 1, access);
conn.lRem(clientId, 1, access);
conn.del(serialize(ACCESS + key));
this.closePipeline(conn);
......@@ -499,30 +496,20 @@ public class CustomRedisTokenStore implements TokenStore {
@Override
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;
RedisConnection conn = getConnection();
try {
byteList = getByteLists(approvalKey, conn);
byteList = conn.lRange(approvalKey, 0, -1);
} finally {
conn.close();
}
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
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;
RedisConnection conn = getConnection();
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;
import com.central.common.feign.UserService;
import com.central.common.resolver.ClientArgumentResolver;
import com.central.common.resolver.TokenArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
......@@ -26,6 +27,9 @@ public class LoginArgResolverConfig implements WebMvcConfigurer {
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注入用户信息
argumentResolvers.add(new TokenArgumentResolver(userService));
//注入应用信息
argumentResolvers.add(new ClientArgumentResolver());
}
}
......@@ -21,6 +21,11 @@ public interface SecurityConstants {
*/
String ROLE_HEADER = "x-role-header";
/**
* 应用信息头
*/
String CLIENT_HEADER = "x-client-header";
/**
* 基础角色
*/
......@@ -140,4 +145,16 @@ public interface SecurityConstants {
* 默认token过期时间(1小时)
*/
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
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 (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 (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);
......@@ -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 (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 (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
......@@ -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, 72);
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, 3);
INSERT INTO `sys_role_menu` VALUES (2, 4);
......
......@@ -2,13 +2,13 @@ package com.central.gateway.filter.pre;
import cn.hutool.core.collection.CollectionUtil;
import com.central.common.constant.SecurityConstants;
import com.central.common.model.SysUser;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
......@@ -40,17 +40,14 @@ public class UserInfoHeaderFilter extends ZuulFilter {
public Object run() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
Object principal = authentication.getPrincipal();
String userInfo;
if (principal instanceof SysUser) {
SysUser user = (SysUser) principal;
userInfo = user.getUsername();
} else {
//jwt的token只有name
userInfo = authentication.getName();
}
String username = authentication.getName();
OAuth2Authentication oauth2Authentication = (OAuth2Authentication)authentication;
String clientId = oauth2Authentication.getOAuth2Request().getClientId();
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(), ","));
}
return null;
......
......@@ -42,7 +42,9 @@ zlt:
menusPaths: /api-user/menus/current
auth:
# 配置必需认证的url
httpUrls: /api-uaa/clients/**
httpUrls: >
/api-uaa/clients/**,
/api-uaa/tokens/**
#是否开启url级别权限
urlEnabled: false
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:
# 忽略认证的地址
httpUrls: >
/validata/**,
/clients,
/clients/**,
/tokens/**,
/login.html,
/css/**,
/images/**,
......
......@@ -84,7 +84,7 @@ layui.define(['config', 'layer'], function (exports) {
return layer.open(param);
},
// 封装ajax请求,返回数据类型为json
req: function (url, data, success, method) {
req: function (url, data, success, method, noHeaderToken) {
if ('put' == method.toLowerCase()) {
method = 'PUT';
} else if ('delete' == method.toLowerCase()) {
......@@ -99,9 +99,11 @@ layui.define(['config', 'layer'], function (exports) {
contentType: "application/json",
success: success,
beforeSend: function (xhr) {
var token = config.getToken();
if (token) {
xhr.setRequestHeader('Authorization', 'bearer ' + token.access_token);
if (!noHeaderToken) {
let token = config.getToken();
if (token) {
xhr.setRequestHeader('Authorization', 'bearer ' + token.access_token);
}
}
}
});
......
......@@ -217,7 +217,7 @@ layui.define(['config', 'admin', 'layer', 'laytpl', 'element', 'form'], function
$('#btnLogout').click(function () {
layer.confirm('确定退出登录?', function () {
//通过认证中心 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();
location.replace('login.html');
}, '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.
先完成此消息的编辑!
想要评论请 注册