From 237641dd296bb1f71424e1b7a6d5a6463ae0761c Mon Sep 17 00:00:00 2001 From: wangxy <1481820854@qq.com> Date: Wed, 18 Dec 2024 14:46:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=98=AF=E5=90=A6=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E8=B4=A6=E6=88=B7=E5=A4=9A=E7=BB=88=E7=AB=AF=E5=90=8C?= =?UTF-8?q?=E6=97=B6=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/ruoyi/common/constant/Constants.java | 6 ++ .../handle/LogoutSuccessHandlerImpl.java | 2 +- .../web/service/SysLoginService.java | 77 +++++++++--------- .../framework/web/service/TokenService.java | 80 +++++++++---------- ruoyi-ui/.env.development | 2 +- ruoyi-ui/.env.production | 2 +- ruoyi-ui/.env.staging | 2 +- ruoyi-ui/src/views/index.vue | 24 +----- ruoyi-ui/src/views/login.vue | 2 +- ruoyi-ui/src/views/register.vue | 2 +- 11 files changed, 92 insertions(+), 109 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 7ffc0f4..6943069 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -106,6 +106,8 @@ token: secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) expireTime: 30 + # 是否允许账户多终端同时登录(true允许 false不允许) + soloLogin: false # MyBatis Plus配置 mybatis-plus: diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index 0c384c6..2477337 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -170,4 +170,10 @@ public class Constants */ public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; + + + /** + * 登录用户编号 redis key + */ + public static final String LOGIN_USERID_KEY = "login_userid:"; } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java index 2f89a91..1f43edc 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -44,7 +44,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { String userName = loginUser.getUsername(); // 删除用户缓存记录 - tokenService.delLoginUser(loginUser.getToken()); + tokenService.delLoginUser(loginUser.getToken(), loginUser.getUser().getUserId()); // 记录用户退出日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java index 80f68f6..8b0c16b 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -7,6 +7,7 @@ import com.anji.captcha.model.vo.CaptchaVO; import com.anji.captcha.service.CaptchaService; import org.hibernate.validator.internal.util.stereotypes.Lazy; 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; @@ -36,12 +37,11 @@ import com.ruoyi.system.service.ISysUserService; /** * 登录校验方法 - * + * * @author ruoyi */ @Component -public class SysLoginService -{ +public class SysLoginService { @Autowired private TokenService tokenService; @@ -50,7 +50,7 @@ public class SysLoginService @Autowired private RedisCache redisCache; - + @Autowired private ISysUserService userService; @@ -61,49 +61,53 @@ public class SysLoginService @Lazy private CaptchaService captchaService; + // 是否允许账户多终端同时登录(true允许 false不允许) + @Value("${token.soloLogin}") + private boolean soloLogin; + + /** * 登录验证 - * + * * @param username 用户名 * @param password 密码 - * @param code 验证码 - * @param uuid 唯一标识 + * @param code 验证码 * @return 结果 */ - public String login(String username, String password, String code) - { + public String login(String username, String password, String code) { // 验证码校验 validateCaptcha(username, code); // 登录前置校验 loginPreCheck(username, password); // 用户验证 Authentication authentication = null; - try - { + try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); - } - catch (Exception e) - { - if (e instanceof BadCredentialsException) - { + } catch (Exception e) { + if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); - } - else - { + } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } - } - finally - { + } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + 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); + } + } recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); @@ -111,18 +115,16 @@ public class SysLoginService /** * 校验验证码 - * + * * @param username 用户名 - * @param code 验证码 + * @param code 验证码 * @return 结果 */ - public void validateCaptcha(String username, String code) - { + public void validateCaptcha(String username, String code) { CaptchaVO captchaVO = new CaptchaVO(); captchaVO.setCaptchaVerification(code); ResponseModel response = captchaService.verification(captchaVO); - if (!response.isSuccess()) - { + if (!response.isSuccess()) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } @@ -130,35 +132,31 @@ public class SysLoginService /** * 登录前置校验 + * * @param username 用户名 * @param password 用户密码 */ - public void loginPreCheck(String username, String password) - { + public void loginPreCheck(String username, String password) { // 用户名或密码为空 错误 - if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) - { + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); throw new UserNotExistsException(); } // 密码如果不在指定范围内 错误 if (password.length() < UserConstants.PASSWORD_MIN_LENGTH - || password.length() > UserConstants.PASSWORD_MAX_LENGTH) - { + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // 用户名不在指定范围内 错误 if (username.length() < UserConstants.USERNAME_MIN_LENGTH - || username.length() > UserConstants.USERNAME_MAX_LENGTH) - { + || username.length() > UserConstants.USERNAME_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // IP黑名单校验 String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); - if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) - { + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); throw new BlackListException(); } @@ -169,8 +167,7 @@ public class SysLoginService * * @param userId 用户ID */ - public void recordLoginInfo(Long userId) - { + public void recordLoginInfo(Long userId) { SysUser sysUser = new SysUser(); sysUser.setUserId(userId); sysUser.setLoginIp(IpUtils.getIpAddr()); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java index aa112da..45fcf28 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -4,6 +4,7 @@ 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; @@ -29,8 +30,7 @@ import io.jsonwebtoken.SignatureAlgorithm; * @author ruoyi */ @Component -public class TokenService -{ +public class TokenService { private static final Logger log = LoggerFactory.getLogger(TokenService.class); // 令牌自定义标识 @@ -45,6 +45,10 @@ public class TokenService @Value("${token.expireTime}") private int expireTime; + // 是否允许账户多终端同时登录(true允许 false不允许) + @Value("${token.soloLogin}") + private boolean soloLogin; + protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; @@ -54,28 +58,24 @@ public class TokenService @Autowired private RedisCache redisCache; + /** * 获取用户身份信息 * * @return 用户信息 */ - public LoginUser getLoginUser(HttpServletRequest request) - { + public LoginUser getLoginUser(HttpServletRequest request) { // 获取请求携带的令牌 String token = getToken(request); - if (StringUtils.isNotEmpty(token)) - { - try - { + if (StringUtils.isNotEmpty(token)) { + try { Claims claims = parseToken(token); // 解析对应的权限以及用户信息 String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); String userKey = getTokenKey(uuid); LoginUser user = redisCache.getCacheObject(userKey); return user; - } - catch (Exception e) - { + } catch (Exception e) { log.error("获取用户信息异常'{}'", e.getMessage()); } } @@ -85,10 +85,8 @@ public class TokenService /** * 设置用户身份信息 */ - public void setLoginUser(LoginUser loginUser) - { - if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) - { + public void setLoginUser(LoginUser loginUser) { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { refreshToken(loginUser); } } @@ -96,13 +94,15 @@ public class TokenService /** * 删除用户身份信息 */ - public void delLoginUser(String token) - { - if (StringUtils.isNotEmpty(token)) - { + 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); + } } /** @@ -111,8 +111,7 @@ public class TokenService * @param loginUser 用户信息 * @return 令牌 */ - public String createToken(LoginUser loginUser) - { + public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); loginUser.setToken(token); setUserAgent(loginUser); @@ -129,12 +128,10 @@ public class TokenService * @param loginUser * @return 令牌 */ - public void verifyToken(LoginUser loginUser) - { + public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); - if (expireTime - currentTime <= MILLIS_MINUTE_TEN) - { + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { refreshToken(loginUser); } } @@ -144,13 +141,17 @@ 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); + if (!soloLogin) { + // 缓存用户唯一标识,防止同一帐号,同时登录 + String userIdKey = getUserIdKey(loginUser.getUser().getUserId()); + redisCache.setCacheObject(userIdKey, userKey, expireTime, TimeUnit.MINUTES); + } } /** @@ -158,8 +159,7 @@ public class TokenService * * @param loginUser 登录信息 */ - public void setUserAgent(LoginUser loginUser) - { + public void setUserAgent(LoginUser loginUser) { UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); String ip = IpUtils.getIpAddr(); loginUser.setIpaddr(ip); @@ -174,8 +174,7 @@ public class TokenService * @param claims 数据声明 * @return 令牌 */ - private String createToken(Map claims) - { + private String createToken(Map claims) { String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret).compact(); @@ -188,8 +187,7 @@ public class TokenService * @param token 令牌 * @return 数据声明 */ - private Claims parseToken(String token) - { + private Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) @@ -202,8 +200,7 @@ public class TokenService * @param token 令牌 * @return 用户名 */ - public String getUsernameFromToken(String token) - { + public String getUsernameFromToken(String token) { Claims claims = parseToken(token); return claims.getSubject(); } @@ -214,18 +211,19 @@ public class TokenService * @param request * @return token */ - private String getToken(HttpServletRequest request) - { + private String getToken(HttpServletRequest request) { String token = request.getHeader(header); - if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) - { + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { token = token.replace(Constants.TOKEN_PREFIX, ""); } return token; } - private String getTokenKey(String uuid) - { + private String getTokenKey(String uuid) { return CacheConstants.LOGIN_TOKEN_KEY + uuid; } + + private String getUserIdKey(Long userId) { + return Constants.LOGIN_USERID_KEY + userId; + } } diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 302ecd1..0ca147a 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 若依管理系统 +VUE_APP_TITLE = 后台管理系统 # 开发环境配置 ENV = 'development' diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index b4893b0..494479f 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 若依管理系统 +VUE_APP_TITLE = 后台管理系统 # 生产环境配置 ENV = 'production' diff --git a/ruoyi-ui/.env.staging b/ruoyi-ui/.env.staging index 361859f..adffd6c 100644 --- a/ruoyi-ui/.env.staging +++ b/ruoyi-ui/.env.staging @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 若依管理系统 +VUE_APP_TITLE = 后台管理系统 NODE_ENV = production diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue index fa54f23..9542b6f 100644 --- a/ruoyi-ui/src/views/index.vue +++ b/ruoyi-ui/src/views/index.vue @@ -30,35 +30,15 @@ icon="el-icon-cloudy" plain @click="goTarget('https://gitee.com/dromara/warm-flow')" - >访问码云 + >访问码云 访问主页 + >访问主页

- - - -
- 捐赠支持 -
-
- donate - 你可以请作者喝杯咖啡表示鼓励 -
-
-
diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue index 15da3cc..9f4f355 100644 --- a/ruoyi-ui/src/views/login.vue +++ b/ruoyi-ui/src/views/login.vue @@ -1,7 +1,7 @@