perf: 优化用户登录Token获取方式

pull/14/head
Carina 4 years ago
parent 91afd5c591
commit 644866a392

@ -5,7 +5,6 @@ import com.google.common.util.concurrent.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opsli.common.thread.ThreadPoolFactory;
import java.util.concurrent.Callable;
import java.util.concurrent.RejectedExecutionException;

@ -1,12 +1,10 @@
package org.opsli.common.thread;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
/**
* 线
@ -41,7 +39,7 @@ public final class ThreadPoolFactory {
* 线
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool(){
public static ExecutorService createDefThreadPool(){
return createInitThreadPool(DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_CONCURRENT * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, DEFAULT_THREAD_POOL_NAME, new ThreadPoolExecutor.CallerRunsPolicy());
}
@ -53,7 +51,7 @@ public final class ThreadPoolFactory {
* @param poolName 线
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool(String poolName){
public static ExecutorService createDefThreadPool(String poolName){
return createInitThreadPool(DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_CONCURRENT * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, poolName, new ThreadPoolExecutor.CallerRunsPolicy());
}
@ -65,7 +63,7 @@ public final class ThreadPoolFactory {
* @param poolName 线
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createDefThreadPool(int maxConcurrent, String poolName){
public static ExecutorService createDefThreadPool(int maxConcurrent, String poolName){
return createInitThreadPool(maxConcurrent, maxConcurrent * 4, DEFAULT_KEEP_ALIVE,
TimeUnit.SECONDS, DEFAULT_SIZE, poolName, new ThreadPoolExecutor.CallerRunsPolicy());
}
@ -81,7 +79,7 @@ public final class ThreadPoolFactory {
* @param handler
* @return ThreadPoolExecutor
*/
public static ThreadPoolExecutor createInitThreadPool(final int coreConcurrent,
public static ExecutorService createInitThreadPool(final int coreConcurrent,
final int maxConcurrent,
final long keepAlive,
final TimeUnit timeUnit,
@ -89,11 +87,11 @@ public final class ThreadPoolFactory {
final String poolName,
final RejectedExecutionHandler handler
){
return new ThreadPoolExecutor(coreConcurrent, maxConcurrent, keepAlive, timeUnit,
return TtlExecutors.getTtlExecutorService(new ThreadPoolExecutor(coreConcurrent, maxConcurrent, keepAlive, timeUnit,
new LinkedBlockingDeque<>(queueSize),
new ThreadFactoryBuilder().setNameFormat(poolName).build(),
handler
);
));
}
private ThreadPoolFactory(){}

@ -19,12 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.opsli.common.annotation.ApiRestController;
import org.opsli.core.api.ApiRequestMappingHandlerMapping;
import org.opsli.core.autoconfigure.properties.ApiPathProperties;
import org.opsli.core.filters.interceptor.UserAuthInterceptor;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@ -83,4 +85,10 @@ public class SpringWebMvcConfig implements WebMvcConfigurer, WebMvcRegistrations
return new CorsFilter(urlBasedCorsConfigurationSource);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 加载特定拦截器
registry.addInterceptor(new UserAuthInterceptor());
WebMvcConfigurer.super.addInterceptors(registry);
}
}

@ -18,27 +18,18 @@ package org.opsli.core.filters.aspect;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.opsli.core.api.TokenThreadLocal;
import org.opsli.common.exception.ServiceException;
import org.opsli.core.utils.LogUtil;
import org.opsli.core.utils.UserTokenUtil;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import static org.opsli.common.constants.OrderConstants.TOKEN_AOP_SORT;
/**
*
*
*
* @author parker
* @date 2020-09-16
@ -47,7 +38,7 @@ import static org.opsli.common.constants.OrderConstants.TOKEN_AOP_SORT;
@Order(TOKEN_AOP_SORT)
@Aspect
@Component
public class TokenAop {
public class LogAop {
@Pointcut("execution(public * org.opsli..*.*Controller*.*(..))")
@ -60,32 +51,6 @@ public class TokenAop {
*/
@Around("requestMapping()")
public Object tokenAop(ProceedingJoinPoint point) throws Throwable {
// Token
String requestToken = TokenThreadLocal.get();
// 如果 ThreadLocal为空 则去当前request中获取
if(StringUtils.isEmpty(requestToken)){
// 将 Token放入 线程缓存
try {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
if(sra != null) {
HttpServletRequest request = sra.getRequest();
requestToken = UserTokenUtil.getRequestToken(request);
if(StringUtils.isNotEmpty(requestToken)){
// 放入当前线程缓存中
TokenThreadLocal.put(requestToken);
}
}
}catch (ServiceException e){
throw e;
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
// 计时器
TimeInterval timer = DateUtil.timer();
// 执行
@ -103,13 +68,7 @@ public class TokenAop {
long timerCount = timer.interval();
//保存日志
LogUtil.saveLog(point, exception, timerCount);
// 线程销毁时 删除 token
if(StringUtils.isNotEmpty(requestToken)){
TokenThreadLocal.remove();
}
}
return returnValue;
}

@ -0,0 +1,61 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.core.filters.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.opsli.core.holder.UserContextHolder;
import org.opsli.core.utils.UserTokenUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* , jwt token
*
* @author
* @date 2021122216:35:20
*/
@Slf4j
public class UserAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler){
if (handler instanceof ResourceHttpRequestHandler) {
return true;
}
try {
String requestToken = UserTokenUtil.getRequestToken(request);
UserContextHolder.setToken(requestToken);
}catch (Exception e){
log.error(e.getMessage(), e);
}
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex){
// 上下文属性值清除,防止内存泄漏
UserContextHolder.clear();
}
}

@ -72,13 +72,24 @@ public class StartPrint {
*
*/
public void errorPrint(){
this.errorPrint(null);
}
/**
*
*
*/
public void errorPrint(String errorMsg){
// 睡一秒打印
ThreadUtil.sleep(1, TimeUnit.SECONDS);
String printStr =
"\n----------------------------------------------------------\n" +
systemName + " 框架启动失败! 请检查相关配置!\n" +
"----------------------------------------------------------\n";
Console.log(printStr);
systemName + " 框架启动失败! 请检查相关配置!\n" +
"----------------------------------------------------------\n";
Console.error(printStr);
if(StringUtils.isNotEmpty(errorMsg)){
Console.error(errorMsg);
}
}

@ -13,9 +13,9 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.core.api;
package org.opsli.core.holder;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.apache.commons.lang3.StringUtils;
import org.opsli.core.utils.UserTokenUtil;
import org.springframework.web.context.request.RequestAttributes;
@ -23,27 +23,32 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
/**
* 线 Token
*
*
* @author parker
* @date 2020-09-15
* @author
* @date 2021122216:22:59
*/
public class TokenThreadLocal {
/** 临时线程存储 token 容器 */
private static final ThreadLocal<String> TOKEN_DATA = new ThreadLocal<>();
public final class UserContextHolder {
public static void put(String token) {
if (TOKEN_DATA.get() == null) {
TOKEN_DATA.set(token);
}
}
/**
*
* 线
*/
public static final ThreadLocal<String> THREAD_LOCAL = new TransmittableThreadLocal<>();
public static String get() {
/**
*
* ,:
* LoginUserInfo loginUserInfo = UserContextHolder.get().orElseThrow(() -> new BusinessException("提示未登录即可"));
*
* @return Optional<LoginUserInfo>
*/
public static Optional<String> getToken() {
String token = TOKEN_DATA.get();
String token = THREAD_LOCAL.get();
// 2021-03-10
// 这里纠正 Token 在被多聚合项目 aop切面 remove后 无法获得Token bug
@ -59,12 +64,24 @@ public class TokenThreadLocal {
}catch (Exception ignored){}
}
return token;
return Optional.ofNullable(token);
}
public static void remove() {
try {
TOKEN_DATA.remove();
}catch (Exception ignored){}
public static void setToken(String token) {
if (THREAD_LOCAL.get() == null) {
THREAD_LOCAL.set(token);
}
}
public static void clear() {
THREAD_LOCAL.remove();
}
/**
*
*/
private UserContextHolder(){}
}

@ -18,7 +18,7 @@ public class ApplicationFailedEventListener implements ApplicationListener<Appli
@Override
public void onApplicationEvent(ApplicationFailedEvent event) {
StartPrint.getInstance().errorPrint();
StartPrint.getInstance().errorPrint(event.getException().getMessage());
}
}

@ -3,7 +3,10 @@ package org.opsli.core.security.shiro.realm;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
@ -11,8 +14,8 @@ import org.apache.shiro.subject.PrincipalCollection;
import org.opsli.api.wrapper.system.tenant.TenantModel;
import org.opsli.api.wrapper.system.user.UserModel;
import org.opsli.common.enums.DictType;
import org.opsli.core.api.TokenThreadLocal;
import org.opsli.common.exception.TokenException;
import org.opsli.core.holder.UserContextHolder;
import org.opsli.core.msg.TokenMsg;
import org.opsli.core.security.shiro.token.JwtToken;
import org.opsli.core.utils.TenantUtil;
@ -119,7 +122,8 @@ public class JwtRealm extends AuthorizingRealm implements FlagRealm {
public static void authToken()
throws TokenException {
String accessToken = TokenThreadLocal.get();
String accessToken = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
// 1. 校验 token 是否有效
boolean verify = UserTokenUtil.verify(accessToken);
@ -151,7 +155,8 @@ public class JwtRealm extends AuthorizingRealm implements FlagRealm {
return;
}
String accessToken = TokenThreadLocal.get();
String accessToken = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
// 查询 用户信息
String userId = UserTokenUtil.getUserIdByToken(accessToken);

@ -34,9 +34,9 @@ import org.opsli.common.constants.TokenConstants;
import org.opsli.common.constants.TokenTypeConstants;
import org.opsli.common.enums.LoginLimitRefuse;
import org.opsli.common.exception.TokenException;
import org.opsli.core.api.TokenThreadLocal;
import org.opsli.core.autoconfigure.properties.GlobalProperties;
import org.opsli.core.cache.CacheUtil;
import org.opsli.core.holder.UserContextHolder;
import org.opsli.core.msg.CoreMsg;
import org.opsli.core.msg.TokenMsg;
import org.opsli.plugins.redis.RedisPlugin;
@ -162,7 +162,10 @@ public class UserTokenUtil {
* @return String
*/
public static String getUserIdByToken() {
return getUserIdByToken(TokenThreadLocal.get());
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
return getUserIdByToken(token);
}
/**
* Token ID
@ -186,7 +189,9 @@ public class UserTokenUtil {
* @return String
*/
public static String getUserNameByToken() {
return getUserNameByToken(TokenThreadLocal.get());
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
return getUserNameByToken(token);
}
/**
* Token
@ -209,7 +214,9 @@ public class UserTokenUtil {
* @return String
*/
public static String getTenantIdByToken() {
return getTenantIdByToken(TokenThreadLocal.get());
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
return getTenantIdByToken(token);
}
/**

@ -31,10 +31,10 @@ import org.opsli.api.wrapper.system.user.UserModel;
import org.opsli.api.wrapper.system.user.UserOrgRefModel;
import org.opsli.common.constants.RedisConstants;
import org.opsli.common.exception.TokenException;
import org.opsli.core.api.TokenThreadLocal;
import org.opsli.core.autoconfigure.properties.GlobalProperties;
import org.opsli.core.cache.CacheUtil;
import org.opsli.core.cache.SecurityCache;
import org.opsli.core.holder.UserContextHolder;
import org.opsli.core.msg.CoreMsg;
import org.opsli.core.msg.TokenMsg;
import org.springframework.beans.factory.annotation.Autowired;
@ -88,7 +88,8 @@ public class UserUtil {
ThrowExceptionUtil.isThrowException(!IS_INIT,
CoreMsg.OTHER_EXCEPTION_UTILS_INIT);
String token = TokenThreadLocal.get();
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
// 如果还是没获取到token 则抛出异常
if(StringUtils.isEmpty(token)){
@ -115,7 +116,8 @@ public class UserUtil {
ThrowExceptionUtil.isThrowException(!IS_INIT,
CoreMsg.OTHER_EXCEPTION_UTILS_INIT);
String token = TokenThreadLocal.get();
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
// 如果还是没获取到token 则抛出异常
if(StringUtils.isEmpty(token)){

@ -26,17 +26,16 @@ import org.opsli.api.wrapper.system.menu.MenuModel;
import org.opsli.api.wrapper.system.options.OptionsModel;
import org.opsli.api.wrapper.system.tenant.TenantModel;
import org.opsli.api.wrapper.system.user.UserModel;
import org.opsli.common.annotation.LoginCrypto;
import org.opsli.common.annotation.Limiter;
import org.opsli.common.enums.DictType;
import org.opsli.common.thread.AsyncProcessExecutor;
import org.opsli.common.thread.AsyncProcessExecutorFactory;
import org.opsli.core.utils.ValidatorUtil;
import org.opsli.core.api.TokenThreadLocal;
import org.opsli.common.annotation.LoginCrypto;
import org.opsli.common.enums.AlertType;
import org.opsli.common.enums.DictType;
import org.opsli.common.enums.OptionsType;
import org.opsli.common.exception.TokenException;
import org.opsli.common.thread.AsyncProcessExecutor;
import org.opsli.common.thread.AsyncProcessExecutorFactory;
import org.opsli.common.utils.IPUtil;
import org.opsli.core.holder.UserContextHolder;
import org.opsli.core.msg.TokenMsg;
import org.opsli.core.utils.*;
import org.opsli.modulars.system.login.entity.LoginForm;
@ -53,6 +52,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* / /
@ -160,6 +160,9 @@ public class LoginRestController {
//生成token并保存到Redis
ResultVo<UserTokenUtil.TokenRet> resultVo = UserTokenUtil.createToken(user);
if(resultVo.isSuccess()){
// 保存Token 到当前线程缓存
UserContextHolder.setToken(resultVo.getData().getToken());
AsyncProcessExecutor normalExecutor = AsyncProcessExecutorFactory.createNormalExecutor();
// 异步保存IP
normalExecutor.put(()->{
@ -167,6 +170,12 @@ public class LoginRestController {
String clientIpAddress = IPUtil.getClientIdBySingle(request);
user.setLoginIp(clientIpAddress);
iUserService.updateLoginIp(user);
// TODO
// 记录用户登录日志 如果系统较大 可考虑 Elastic 的 filebeat
// 小系统 直接存在 mysql就好
});
normalExecutor.execute();
}
@ -181,7 +190,9 @@ public class LoginRestController {
@ApiOperation(value = "登出", notes = "登出")
@PostMapping("/system/logout")
public ResultVo<?> logout() {
String token = TokenThreadLocal.get();
String token = UserContextHolder.getToken().orElseThrow(() -> new TokenException(
TokenMsg.EXCEPTION_TOKEN_LOSE_EFFICACY));
// 登出失败没有授权Token
if(StringUtils.isEmpty(token)){
return ResultVo.error(TokenMsg.EXCEPTION_LOGOUT_ERROR.getMessage());

@ -301,6 +301,12 @@
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.5</version>
</dependency>
<!-- 糊涂工具包 -->
<dependency>
<groupId>cn.hutool</groupId>

Loading…
Cancel
Save