From dc04eea7c691ed3aefbc5df2cf3162eb2277d925 Mon Sep 17 00:00:00 2001 From: hansha Date: Sat, 15 Jun 2024 16:57:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=99=90=E5=88=B6=E7=BB=9F=E4=B8=80=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E4=B8=8D=E5=90=8C=E7=BB=88=E7=AB=AF=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 2 + .../com/da/common/constant/Constants.java | 8 ++- .../handle/LogoutSuccessHandlerImpl.java | 38 +++++++++--- .../web/service/SysLoginService.java | 40 ++++++++---- .../framework/web/service/TokenService.java | 61 +++++++++++++++---- dangan-ui/src/utils/request.js | 3 +- 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/dangan-admin/src/main/resources/application.yml b/dangan-admin/src/main/resources/application.yml index 8710dc8..d35ce56 100644 --- a/dangan-admin/src/main/resources/application.yml +++ b/dangan-admin/src/main/resources/application.yml @@ -90,6 +90,8 @@ spring: # token配置 token: + #是否允许账户多终端同时登录(true允许 false不允许) + soloLogin: true # 令牌自定义标识 header: Authorization # 令牌密钥 diff --git a/dangan-common/src/main/java/com/da/common/constant/Constants.java b/dangan-common/src/main/java/com/da/common/constant/Constants.java index e579519..e0075df 100644 --- a/dangan-common/src/main/java/com/da/common/constant/Constants.java +++ b/dangan-common/src/main/java/com/da/common/constant/Constants.java @@ -1,8 +1,9 @@ package com.da.common.constant; -import java.util.Locale; import io.jsonwebtoken.Claims; +import java.util.Locale; + /** * 通用常量信息 * @@ -10,6 +11,11 @@ import io.jsonwebtoken.Claims; */ public class Constants { + /** + *登录用户编号 redis key + */ + public static final String LOGIN_USERID_KEY = "login_userid:"; + /** * UTF-8 字符集 */ diff --git a/dangan-framework/src/main/java/com/da/framework/security/handle/LogoutSuccessHandlerImpl.java b/dangan-framework/src/main/java/com/da/framework/security/handle/LogoutSuccessHandlerImpl.java index b7ff997..42b6793 100644 --- a/dangan-framework/src/main/java/com/da/framework/security/handle/LogoutSuccessHandlerImpl.java +++ b/dangan-framework/src/main/java/com/da/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -1,23 +1,23 @@ package com.da.framework.security.handle; -import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import com.alibaba.fastjson2.JSON; import com.da.common.constant.Constants; import com.da.common.core.domain.AjaxResult; import com.da.common.core.domain.model.LoginUser; -import com.da.common.utils.MessageUtils; import com.da.common.utils.ServletUtils; import com.da.common.utils.StringUtils; import com.da.framework.manager.AsyncManager; import com.da.framework.manager.factory.AsyncFactory; import com.da.framework.web.service.TokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * 自定义退出处理类 返回成功 @@ -36,7 +36,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler * @return */ @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + /*public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { LoginUser loginUser = tokenService.getLoginUser(request); @@ -49,5 +49,23 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); } ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + }*/ + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录(旧方法) + //tokenService.delLoginUser(loginUser.getToken()); + + // 删除用户缓存记录 只允许单用户登录(关键) + tokenService.delLoginUser(loginUser.getToken(), loginUser.getUser().getUserId()); + + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功"))); } } diff --git a/dangan-framework/src/main/java/com/da/framework/web/service/SysLoginService.java b/dangan-framework/src/main/java/com/da/framework/web/service/SysLoginService.java index dcfe7e2..124e37d 100644 --- a/dangan-framework/src/main/java/com/da/framework/web/service/SysLoginService.java +++ b/dangan-framework/src/main/java/com/da/framework/web/service/SysLoginService.java @@ -1,12 +1,5 @@ package com.da.framework.web.service; -import javax.annotation.Resource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; import com.da.common.constant.CacheConstants; import com.da.common.constant.Constants; import com.da.common.constant.UserConstants; @@ -14,11 +7,7 @@ import com.da.common.core.domain.entity.SysUser; import com.da.common.core.domain.model.LoginUser; import com.da.common.core.redis.RedisCache; import com.da.common.exception.ServiceException; -import com.da.common.exception.user.BlackListException; -import com.da.common.exception.user.CaptchaException; -import com.da.common.exception.user.CaptchaExpireException; -import com.da.common.exception.user.UserNotExistsException; -import com.da.common.exception.user.UserPasswordNotMatchException; +import com.da.common.exception.user.*; import com.da.common.utils.DateUtils; import com.da.common.utils.MessageUtils; import com.da.common.utils.StringUtils; @@ -28,6 +17,15 @@ import com.da.framework.manager.factory.AsyncFactory; import com.da.framework.security.context.AuthenticationContextHolder; import com.da.system.service.ISysConfigService; import com.da.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; /** * 登录校验方法 @@ -37,6 +35,9 @@ import com.da.system.service.ISysUserService; @Component public class SysLoginService { + // 是否允许账户多终端同时登录(true允许 false不允许) + @Value("${token.soloLogin}") + private boolean soloLogin; @Autowired private TokenService tokenService; @@ -96,6 +97,21 @@ public class SysLoginService AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); + + //---------------------------------------添加如下代码,验证如果用户不允许多终端同时登录,清除缓存信息 + if (!soloLogin) + { + // 如果用户不允许多终端同时登录,清除缓存信息 + String userIdKey = Constants.LOGIN_USERID_KEY + loginUser.getUser().getUserId(); + String userKey = redisCache.getCacheObject(userIdKey); + if (StringUtils.isNotEmpty(userKey)) + { + redisCache.deleteObject(userIdKey); + redisCache.deleteObject(userKey); + } + } + //----------------新增代码结束 + // 生成token return tokenService.createToken(loginUser); } diff --git a/dangan-framework/src/main/java/com/da/framework/web/service/TokenService.java b/dangan-framework/src/main/java/com/da/framework/web/service/TokenService.java index 42ed2d5..23e382d 100644 --- a/dangan-framework/src/main/java/com/da/framework/web/service/TokenService.java +++ b/dangan-framework/src/main/java/com/da/framework/web/service/TokenService.java @@ -1,14 +1,5 @@ package com.da.framework.web.service; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import javax.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import com.da.common.constant.CacheConstants; import com.da.common.constant.Constants; import com.da.common.core.domain.model.LoginUser; @@ -22,6 +13,16 @@ import eu.bitwalker.useragentutils.UserAgent; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * token验证处理 @@ -33,6 +34,10 @@ public class TokenService { private static final Logger log = LoggerFactory.getLogger(TokenService.class); + // 是否允许账户多终端同时登录(true允许 false不允许) + @Value("${token.soloLogin}") + private boolean soloLogin; + // 令牌自定义标识 @Value("${token.header}") private String header; @@ -96,15 +101,29 @@ public class TokenService /** * 删除用户身份信息 */ - public void delLoginUser(String token) +/* public void delLoginUser(String token) { if (StringUtils.isNotEmpty(token)) { String userKey = getTokenKey(token); redisCache.deleteObject(userKey); } + }*/ + public void delLoginUser(String token, Long userId) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + if (!soloLogin && StringUtils.isNotNull(userId)) + { + String userIdKey = getUserIdKey(userId); + redisCache.deleteObject(userIdKey); + } } + /** * 创建令牌 * @@ -144,13 +163,28 @@ public class TokenService * * @param loginUser 登录信息 */ - public void refreshToken(LoginUser loginUser) +/* public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + }*/ + public String refreshToken(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser缓存 String userKey = getTokenKey(loginUser.getToken()); redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + if (!soloLogin) + { + // 缓存用户唯一标识,防止同一帐号,同时登录 + String userIdKey = getUserIdKey(loginUser.getUser().getUserId()); + redisCache.setCacheObject(userIdKey, userKey, expireTime, TimeUnit.MINUTES); + } + return userKey; } /** @@ -168,6 +202,11 @@ public class TokenService loginUser.setOs(userAgent.getOperatingSystem().getName()); } + private String getUserIdKey(Long userId) + { + return Constants.LOGIN_USERID_KEY + userId; + } + /** * 从数据声明生成令牌 * diff --git a/dangan-ui/src/utils/request.js b/dangan-ui/src/utils/request.js index 74d3db7..a4eb298 100644 --- a/dangan-ui/src/utils/request.js +++ b/dangan-ui/src/utils/request.js @@ -93,7 +93,8 @@ service.interceptors.response.use(res => { isRelogin.show = false; }); } - return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + //return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + return Promise.reject('会话已过期,或者被挤下线,请重新登录。') } else if (code === 500) { Message({ message: msg, type: 'error' }) return Promise.reject(new Error(msg))