From a75988ff23db6bd31bc79d004338ac3376ed51d0 Mon Sep 17 00:00:00 2001 From: wangxy <1356089412@qq.com> Date: Tue, 4 Jun 2024 09:29:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BF=AE=E6=94=B9=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E8=B4=A6=E6=88=B7=E5=A4=9A=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hyp-admin/src/main/resources/application.yml | 2 + .../com/hyp/common/constant/Constants.java | 6 ++ .../handle/LogoutSuccessHandlerImpl.java | 5 +- .../web/service/SysLoginService.java | 76 +++++++++---------- .../framework/web/service/TokenService.java | 26 ++++++- 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/hyp-admin/src/main/resources/application.yml b/hyp-admin/src/main/resources/application.yml index 85e8432..3e501d5 100644 --- a/hyp-admin/src/main/resources/application.yml +++ b/hyp-admin/src/main/resources/application.yml @@ -96,6 +96,8 @@ token: secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) expireTime: 30 + # 是否允许账户多终端同时登录(true允许 false不允许) + soloLogin: false # MyBatis Plus配置 mybatis-plus: diff --git a/hyp-common/src/main/java/com/hyp/common/constant/Constants.java b/hyp-common/src/main/java/com/hyp/common/constant/Constants.java index 9d26920..c6d5218 100644 --- a/hyp-common/src/main/java/com/hyp/common/constant/Constants.java +++ b/hyp-common/src/main/java/com/hyp/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.hyp.common.utils.file", "com.hyp.common.config", "com.hyp.generator" }; + + + /** + * 登录用户编号 redis key + */ + public static final String LOGIN_USERID_KEY = "login_userid:"; } diff --git a/hyp-framework/src/main/java/com/hyp/framework/security/handle/LogoutSuccessHandlerImpl.java b/hyp-framework/src/main/java/com/hyp/framework/security/handle/LogoutSuccessHandlerImpl.java index 23229b3..ca4c60c 100644 --- a/hyp-framework/src/main/java/com/hyp/framework/security/handle/LogoutSuccessHandlerImpl.java +++ b/hyp-framework/src/main/java/com/hyp/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -29,10 +29,9 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { @Autowired private TokenService tokenService; - /** * 退出处理 - * + * * @return */ @Override @@ -44,7 +43,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/hyp-framework/src/main/java/com/hyp/framework/web/service/SysLoginService.java b/hyp-framework/src/main/java/com/hyp/framework/web/service/SysLoginService.java index 4caf93d..305d4bf 100644 --- a/hyp-framework/src/main/java/com/hyp/framework/web/service/SysLoginService.java +++ b/hyp-framework/src/main/java/com/hyp/framework/web/service/SysLoginService.java @@ -5,6 +5,7 @@ import javax.annotation.Resource; import com.anji.captcha.model.common.ResponseModel; import com.anji.captcha.model.vo.CaptchaVO; import com.anji.captcha.service.CaptchaService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; @@ -34,12 +35,11 @@ import com.hyp.system.service.ISysUserService; /** * 登录校验方法 - * + * * @author ruoyi */ @Component -public class SysLoginService -{ +public class SysLoginService { @Autowired private TokenService tokenService; @@ -48,7 +48,7 @@ public class SysLoginService @Autowired private RedisCache redisCache; - + @Autowired private ISysUserService userService; @@ -59,48 +59,54 @@ public class SysLoginService @Lazy private CaptchaService captchaService; + // 是否允许账户多终端同时登录(true允许 false不允许) + @Value("${token.soloLogin}") + private boolean soloLogin; + /** * 登录验证 * * @param username 用户名 * @param password 密码 - * @param code 验证码 + * @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); @@ -110,19 +116,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) { boolean captchaEnabled = configService.selectCaptchaEnabled(); - if (captchaEnabled) - { + if (captchaEnabled) { 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(); } @@ -131,35 +134,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(); } @@ -170,8 +169,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/hyp-framework/src/main/java/com/hyp/framework/web/service/TokenService.java b/hyp-framework/src/main/java/com/hyp/framework/web/service/TokenService.java index cd2ca33..88246ef 100644 --- a/hyp-framework/src/main/java/com/hyp/framework/web/service/TokenService.java +++ b/hyp-framework/src/main/java/com/hyp/framework/web/service/TokenService.java @@ -45,6 +45,11 @@ 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; @@ -96,13 +101,18 @@ public class TokenService /** * 删除用户身份信息 */ - public void delLoginUser(String 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); + } } /** @@ -151,8 +161,17 @@ public class TokenService // 根据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); + } } + + + /** * 设置用户代理信息 * @@ -228,4 +247,9 @@ public class TokenService { return CacheConstants.LOGIN_TOKEN_KEY + uuid; } + + private String getUserIdKey(Long userId) + { + return Constants.LOGIN_USERID_KEY + userId; + } }