🔨 修改认证逻辑.

This commit is contained in:
lijiahangmax
2025-10-30 16:42:43 +08:00
parent 5d86c330fe
commit 83c64dddfb
8 changed files with 144 additions and 120 deletions

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.module.infra.api.impl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage; import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Assert; import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.module.infra.api.AuthenticationApi; import org.dromara.visor.module.infra.api.AuthenticationApi;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO; import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.user.SystemUserAuthDTO; import org.dromara.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
@@ -57,7 +58,11 @@ public class AuthenticationApiImpl implements AuthenticationApi {
result.setUsername(user.getUsername()); result.setUsername(user.getUsername());
result.setNickname(user.getNickname()); result.setNickname(user.getNickname());
// 检查用户密码 // 检查用户密码
boolean passRight = authenticationService.checkUserPassword(user, password, addFailedCount); boolean passRight = authenticationService.checkUserPassword(user, password);
if (!passRight && addFailedCount) {
// 发送站内信
authenticationService.addLoginFailedCount(user.getUsername(), Requests.getIdentity());
}
result.setPassRight(passRight); result.setPassRight(passRight);
Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR); Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态 // 检查用户状态

View File

@@ -63,9 +63,8 @@ public class AuthenticationController {
@PermitAll @PermitAll
@Operation(summary = "登录") @Operation(summary = "登录")
@PostMapping("/login") @PostMapping("/login")
public UserLoginVO login(@Validated @RequestBody UserLoginRequest request, public UserLoginVO login(@Validated @RequestBody UserLoginRequest request) {
HttpServletRequest servletRequest) { return authenticationService.login(request);
return authenticationService.login(request, servletRequest);
} }
@OperatorLog(AuthenticationOperatorType.LOGOUT) @OperatorLog(AuthenticationOperatorType.LOGOUT)

View File

@@ -26,7 +26,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum; import org.dromara.visor.common.entity.RequestIdentityModel;
/** /**
* 登录 token 缓存 * 登录 token 缓存
@@ -42,14 +42,19 @@ import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
public class LoginTokenDTO { public class LoginTokenDTO {
/** /**
* 用户id * userId
*/ */
private Long id; private Long id;
/**
* 用户名
*/
private String username;
/** /**
* token 状态 * token 状态
* *
* @see LoginTokenStatusEnum * @see org.dromara.visor.module.infra.enums.LoginTokenStatusEnum
*/ */
private Integer status; private Integer status;
@@ -59,13 +64,13 @@ public class LoginTokenDTO {
private Integer refreshCount; private Integer refreshCount;
/** /**
* 原始登录身份 * 原始登录留痕信息
*/ */
private LoginTokenIdentityDTO origin; private RequestIdentityModel origin;
/** /**
* 覆盖登录身份 * 覆盖登录刘海信息
*/ */
private LoginTokenIdentityDTO override; private RequestIdentityModel override;
} }

View File

@@ -26,8 +26,8 @@ import cn.orionsec.kit.lang.utils.time.Dates;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.dromara.visor.common.constant.ErrorCode; import org.dromara.visor.common.constant.ErrorCode;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO; import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import java.util.Date; import java.util.Date;
@@ -53,9 +53,9 @@ public enum LoginTokenStatusEnum {
OTHER_DEVICE(1) { OTHER_DEVICE(1) {
@Override @Override
public RuntimeException toException(LoginTokenDTO token) { public RuntimeException toException(LoginTokenDTO token) {
LoginTokenIdentityDTO override = token.getOverride(); RequestIdentityModel override = token.getOverride();
return ErrorCode.USER_OTHER_DEVICE_LOGIN.exception( return ErrorCode.USER_OTHER_DEVICE_LOGIN.exception(
Dates.format(new Date(override.getLoginTime()), Dates.MD_HM), Dates.format(new Date(override.getTimestamp()), Dates.MD_HM),
override.getAddress(), override.getAddress(),
override.getLocation()); override.getLocation());
} }
@@ -68,9 +68,9 @@ public enum LoginTokenStatusEnum {
SESSION_OFFLINE(2) { SESSION_OFFLINE(2) {
@Override @Override
public RuntimeException toException(LoginTokenDTO token) { public RuntimeException toException(LoginTokenDTO token) {
LoginTokenIdentityDTO override = token.getOverride(); RequestIdentityModel override = token.getOverride();
return ErrorCode.USER_OFFLINE.exception( return ErrorCode.USER_OFFLINE.exception(
Dates.format(new Date(override.getLoginTime()), Dates.MD_HM), Dates.format(new Date(override.getTimestamp()), Dates.MD_HM),
override.getAddress(), override.getAddress(),
override.getLocation()); override.getLocation());
} }

View File

@@ -82,12 +82,13 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
if (tokenInfo == null) { if (tokenInfo == null) {
return null; return null;
} }
Long loginTime = tokenInfo.getOrigin().getTimestamp();
try { try {
// 检查 token 状态 // 检查 token 状态
this.checkTokenStatus(tokenInfo); this.checkTokenStatus(tokenInfo);
} catch (Exception e) { } catch (Exception e) {
// token 失效则删除 // token 失效则删除
RedisUtils.delete(UserCacheKeyDefine.LOGIN_TOKEN.format(tokenInfo.getId(), tokenInfo.getOrigin().getLoginTime())); RedisUtils.delete(UserCacheKeyDefine.LOGIN_TOKEN.format(tokenInfo.getId(), loginTime));
throw e; throw e;
} }
// 获取登录信息 // 获取登录信息
@@ -98,7 +99,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
// 检查用户状态 // 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus()); UserStatusEnum.checkUserStatus(user.getStatus());
// 设置登录时间戳 // 设置登录时间戳
user.setTimestamp(tokenInfo.getOrigin().getLoginTime()); user.setTimestamp(loginTime);
return user; return user;
} }

View File

@@ -22,6 +22,7 @@
*/ */
package org.dromara.visor.module.infra.service; package org.dromara.visor.module.infra.service;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.common.security.LoginUser; import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO; import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO; import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
@@ -42,11 +43,10 @@ public interface AuthenticationService {
/** /**
* 登录 * 登录
* *
* @param request request * @param request request
* @param servletRequest servletRequest
* @return login * @return login
*/ */
UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest); UserLoginVO login(UserLoginRequest request);
/** /**
* 登出 * 登出
@@ -83,12 +83,19 @@ public interface AuthenticationService {
/** /**
* 检查用户密码 * 检查用户密码
* *
* @param user user * @param user user
* @param password password * @param password password
* @param addFailedCount addFailedCount
* @return passRight * @return passRight
*/ */
boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount); boolean checkUserPassword(SystemUserDO user, String password);
/**
* 添加登录失败次数
*
* @param username username
* @param identity identity
*/
void addLoginFailedCount(String username, RequestIdentityModel identity);
/** /**
* 检查用户状态 * 检查用户状态

View File

@@ -22,29 +22,26 @@
*/ */
package org.dromara.visor.module.infra.service.impl; package org.dromara.visor.module.infra.service.impl;
import cn.orionsec.kit.lang.annotation.Keep;
import cn.orionsec.kit.lang.define.wrapper.Pair; import cn.orionsec.kit.lang.define.wrapper.Pair;
import cn.orionsec.kit.lang.utils.Booleans; import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Exceptions; import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.crypto.Signatures; import cn.orionsec.kit.lang.utils.crypto.Signatures;
import cn.orionsec.kit.lang.utils.time.Dates; import cn.orionsec.kit.lang.utils.time.Dates;
import cn.orionsec.kit.web.servlet.web.Servlets;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import org.dromara.visor.common.config.ConfigStore; import org.dromara.visor.common.config.ConfigStore;
import org.dromara.visor.common.constant.ConfigKeys; import org.dromara.visor.common.constant.ConfigKeys;
import org.dromara.visor.common.constant.Const; import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage; import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.constant.ExtraFieldConst; import org.dromara.visor.common.constant.ExtraFieldConst;
import org.dromara.visor.common.entity.RequestIdentity;
import org.dromara.visor.common.entity.RequestIdentityModel;
import org.dromara.visor.common.security.LoginUser; import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.common.security.UserRole; import org.dromara.visor.common.security.UserRole;
import org.dromara.visor.common.utils.AesEncryptUtils; import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.common.utils.Assert; import org.dromara.visor.common.utils.Assert;
import org.dromara.visor.common.utils.IpUtils; import org.dromara.visor.common.utils.Requests;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs; import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.redis.core.utils.RedisStrings; import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.framework.redis.core.utils.RedisUtils;
import org.dromara.visor.framework.security.core.utils.SecurityUtils; import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.common.config.AppLoginConfig; import org.dromara.visor.module.common.config.AppLoginConfig;
import org.dromara.visor.module.infra.api.SystemMessageApi; import org.dromara.visor.module.infra.api.SystemMessageApi;
@@ -54,8 +51,8 @@ import org.dromara.visor.module.infra.dao.SystemUserRoleDAO;
import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine; import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine;
import org.dromara.visor.module.infra.define.message.SystemUserMessageDefine; import org.dromara.visor.module.infra.define.message.SystemUserMessageDefine;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO; import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.LoginFailedDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO; import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
import org.dromara.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO; import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO;
import org.dromara.visor.module.infra.entity.request.user.UserLoginRequest; import org.dromara.visor.module.infra.entity.request.user.UserLoginRequest;
import org.dromara.visor.module.infra.entity.vo.UserLoginVO; import org.dromara.visor.module.infra.entity.vo.UserLoginVO;
@@ -63,7 +60,6 @@ import org.dromara.visor.module.infra.enums.LoginTokenStatusEnum;
import org.dromara.visor.module.infra.enums.UserStatusEnum; import org.dromara.visor.module.infra.enums.UserStatusEnum;
import org.dromara.visor.module.infra.service.AuthenticationService; import org.dromara.visor.module.infra.service.AuthenticationService;
import org.dromara.visor.module.infra.service.UserPermissionService; import org.dromara.visor.module.infra.service.UserPermissionService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@@ -98,10 +94,6 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Resource @Resource
private SystemMessageApi systemMessageApi; private SystemMessageApi systemMessageApi;
@Keep
@Resource
private RedisTemplate<String, String> redisTemplate;
@Resource @Resource
private ConfigStore configStore; private ConfigStore configStore;
@@ -110,25 +102,29 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 监听并且设置缓存过期时间 // 监听并且设置缓存过期时间
configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME).onChange((v, b) -> this.setCacheExpireTime()); configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME).onChange((v, b) -> this.setCacheExpireTime());
configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL).onChange((v, b) -> this.setCacheExpireTime()); configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL).onChange((v, b) -> this.setCacheExpireTime());
configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_TIME).onChange((v, b) -> this.setCacheExpireTime());
this.setCacheExpireTime(); this.setCacheExpireTime();
} }
@Override @Override
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) { public UserLoginVO login(UserLoginRequest request) {
// 获取登录信息 // 获取登录痕迹
String remoteAddr = IpUtils.getRemoteAddr(servletRequest); String username = request.getUsername();
String location = IpUtils.getLocation(remoteAddr); RequestIdentityModel identity = Requests.getIdentity();
String userAgent = Servlets.getUserAgent(servletRequest);
// 设置日志上下文的用户 否则登录失败不会记录日志 // 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request)); OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查 // 登录前检查
SystemUserDO user = this.preCheckLogin(request.getUsername(), request.getPassword()); SystemUserDO user = this.preCheckLogin(username, request.getPassword());
// 重新设置日志上下文 // 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user)); OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 用户密码校验 // 用户密码校验
boolean passRight = this.checkUserPassword(user, request.getPassword(), true); boolean passRight = this.checkUserPassword(user, request.getPassword());
// 发送站内信 if (!passRight) {
this.sendLoginFailedErrorMessage(passRight, user, remoteAddr, location); // 增加登录失败次数
this.addLoginFailedCount(username, identity);
// 登录失败发送站内信
this.sendLoginFailedErrorMessage(user, identity);
}
Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR); Assert.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 用户状态校验 // 用户状态校验
this.checkUserStatus(user); this.checkUserStatus(user);
@@ -139,14 +135,13 @@ public class AuthenticationServiceImpl implements AuthenticationService {
this.deleteUserCache(user); this.deleteUserCache(user);
// 重设用户缓存 // 重设用户缓存
this.setUserCache(user); this.setUserCache(user);
long current = System.currentTimeMillis();
// 不允许多端登录 // 不允许多端登录
if (Booleans.isFalse(appLoginConfig.getAllowMultiDevice())) { if (Booleans.isFalse(appLoginConfig.getAllowMultiDevice())) {
// 无效化其他缓存 // 无效化其他缓存
this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent); this.invalidOtherDeviceToken(id, identity);
} }
// 生成 loginToken // 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent); String token = this.generatorLoginToken(user, identity);
return UserLoginVO.builder() return UserLoginVO.builder()
.token(token) .token(token)
.build(); .build();
@@ -169,16 +164,16 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 删除 loginToken & refreshToken // 删除 loginToken & refreshToken
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, current); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, current);
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, current); String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, current);
redisTemplate.delete(Lists.of(loginKey, refreshKey)); RedisStrings.delete(loginKey, refreshKey);
} }
@Override @Override
public LoginUser getLoginUser(Long id) { public LoginUser getLoginUser(Long id) {
// 查询缓存用户信息
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(id); String userInfoKey = UserCacheKeyDefine.USER_INFO.format(id);
String userInfoCache = redisTemplate.opsForValue().get(userInfoKey); LoginUser loginUser = RedisStrings.getJson(userInfoKey, UserCacheKeyDefine.USER_INFO);
// 缓存存在 if (loginUser != null) {
if (userInfoCache != null) { return loginUser;
return JSON.parseObject(userInfoCache, LoginUser.class);
} }
// 查询用户信息 // 查询用户信息
SystemUserDO user = systemUserDAO.selectById(id); SystemUserDO user = systemUserDAO.selectById(id);
@@ -198,22 +193,21 @@ public class AuthenticationServiceImpl implements AuthenticationService {
} }
// 获取登录 key value // 获取登录 key value
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(pair.getKey(), pair.getValue()); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(pair.getKey(), pair.getValue());
String loginCache = redisTemplate.opsForValue().get(loginKey); LoginTokenDTO loginCache = RedisStrings.getJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN);
if (loginCache != null) { if (loginCache != null) {
return JSON.parseObject(loginCache, LoginTokenDTO.class); return loginCache;
} }
// loginToken 不存在 需要查询 refreshToken // loginToken 不存在 需要查询 refreshToken
if (Booleans.isFalse(appLoginConfig.getAllowRefresh())) { if (Booleans.isFalse(appLoginConfig.getAllowRefresh())) {
return null; return null;
} }
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue()); String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue());
String refreshCache = redisTemplate.opsForValue().get(refreshKey); LoginTokenDTO refresh = RedisStrings.getJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH);
// 未查询到刷新key直接返回 // 未查询到 refreshToken 直接返回
if (refreshCache == null) { if (refresh == null) {
return null; return null;
} }
// 执行续签操作 // 执行续签操作
LoginTokenDTO refresh = JSON.parseObject(refreshCache, LoginTokenDTO.class);
int refreshCount = refresh.getRefreshCount() + 1; int refreshCount = refresh.getRefreshCount() + 1;
refresh.setRefreshCount(refreshCount); refresh.setRefreshCount(refreshCount);
// 设置登录缓存 // 设置登录缓存
@@ -223,7 +217,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh); RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh);
} else { } else {
// 大于等于续签最大次数 则删除 // 大于等于续签最大次数 则删除
redisTemplate.delete(refreshKey); RedisStrings.delete(refreshKey);
} }
return refresh; return refresh;
} }
@@ -236,11 +230,14 @@ public class AuthenticationServiceImpl implements AuthenticationService {
} }
// 检查登录失败次数锁定 // 检查登录失败次数锁定
if (Booleans.isTrue(appLoginConfig.getLoginFailedLock())) { if (Booleans.isTrue(appLoginConfig.getLoginFailedLock())) {
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username); String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(username);
String failedCount = redisTemplate.opsForValue().get(failedCountKey); LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
if (failedCount != null Integer failedCount = Optional.ofNullable(loginFailed)
&& Integer.parseInt(failedCount) >= appLoginConfig.getLoginFailedLockThreshold()) { .map(LoginFailedDTO::getFailedCount)
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED); .orElse(null);
// 检查是否超过失败次数
if (failedCount != null && failedCount >= appLoginConfig.getLoginFailedLockThreshold()) {
Assert.lt(failedCount, appLoginConfig.getLoginFailedLockThreshold(), ErrorMessage.MAX_LOGIN_FAILED);
} }
} }
// 获取登录用户 // 获取登录用户
@@ -254,16 +251,32 @@ public class AuthenticationServiceImpl implements AuthenticationService {
} }
@Override @Override
public boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount) { public boolean checkUserPassword(SystemUserDO user, String password) {
// 检查密码 return user.getPassword().equals(Signatures.md5(password));
boolean passRight = user.getPassword().equals(Signatures.md5(password)); }
if (!passRight && addFailedCount) {
// 刷新登录失败缓存 @Override
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername()); public void addLoginFailedCount(String username, RequestIdentityModel identity) {
redisTemplate.opsForValue().increment(failedCountKey); // 过期时间
RedisUtils.setExpire(failedCountKey, appLoginConfig.getLoginFailedLockTime(), TimeUnit.MINUTES); long expireTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(UserCacheKeyDefine.LOGIN_FAILED.getTimeout());
// 刷新登录失败缓存
String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(username);
LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
if (loginFailed == null) {
// 首次登录失败
loginFailed = LoginFailedDTO.builder()
.username(username)
.failedCount(1)
.expireTime(expireTime)
.origin(identity)
.build();
} else {
// 非首次登录失败
loginFailed.setExpireTime(expireTime);
loginFailed.setFailedCount(loginFailed.getFailedCount() + 1);
} }
return passRight; // 重新设置缓存
RedisStrings.setJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED, loginFailed);
} }
@Override @Override
@@ -275,33 +288,30 @@ public class AuthenticationServiceImpl implements AuthenticationService {
/** /**
* 发送登录失败错误消息 * 发送登录失败错误消息
* *
* @param passRight passRight * @param user user
* @param user user * @param identity identity
* @param remoteAddr remoteAddr
* @param location location
*/ */
private void sendLoginFailedErrorMessage(boolean passRight, SystemUserDO user, private void sendLoginFailedErrorMessage(SystemUserDO user, RequestIdentity identity) {
String remoteAddr, String location) {
if (passRight) {
return;
}
// 检查是否开启登录失败发信 // 检查是否开启登录失败发信
if (!Booleans.isTrue(appLoginConfig.getLoginFailedSend())) { if (!Booleans.isTrue(appLoginConfig.getLoginFailedSend())) {
return; return;
} }
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername()); String loginFailedKey = UserCacheKeyDefine.LOGIN_FAILED.format(user.getUsername());
String failedCountStr = redisTemplate.opsForValue().get(failedCountKey); LoginFailedDTO loginFailed = RedisStrings.getJson(loginFailedKey, UserCacheKeyDefine.LOGIN_FAILED);
if (failedCountStr == null || !Strings.isInteger(failedCountStr)) { Integer failedCount = Optional.ofNullable(loginFailed)
.map(LoginFailedDTO::getFailedCount)
.orElse(null);
if (failedCount == null) {
return; return;
} }
// 直接用相等 因为只触发一次 // 直接用相等 因为只触发一次
if (!Integer.valueOf(failedCountStr).equals(appLoginConfig.getLoginFailedSendThreshold())) { if (!failedCount.equals(appLoginConfig.getLoginFailedSendThreshold())) {
return; return;
} }
// 发送站内信 // 发送站内信
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ADDRESS, remoteAddr); params.put(ExtraFieldConst.ADDRESS, identity.getAddress());
params.put(ExtraFieldConst.LOCATION, location); params.put(ExtraFieldConst.LOCATION, identity.getLocation());
params.put(ExtraFieldConst.TIME, Dates.current()); params.put(ExtraFieldConst.TIME, Dates.current());
SystemMessageDTO message = SystemMessageDTO.builder() SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(user.getId()) .receiverId(user.getId())
@@ -334,9 +344,9 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 用户信息缓存 // 用户信息缓存
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId());
// 登录失败次数缓存 // 登录失败次数缓存
String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername()); String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED.format(user.getUsername());
// 删除缓存 // 删除缓存
redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey)); RedisStrings.delete(userInfoKey, loginFailedCountKey);
} }
/** /**
@@ -385,21 +395,16 @@ public class AuthenticationServiceImpl implements AuthenticationService {
/** /**
* 无效化其他登录信息 * 无效化其他登录信息
* *
* @param id id * @param id id
* @param loginTime loginTime * @param identity identity
* @param remoteAddr remoteAddr
* @param location location
* @param userAgent userAgent
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
private void invalidOtherDeviceToken(Long id, long loginTime, private void invalidOtherDeviceToken(Long id, RequestIdentityModel identity) {
String remoteAddr, String location, String userAgent) {
// 获取登录信息 // 获取登录信息
Set<String> loginKeyList = RedisUtils.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*")); Set<String> loginKeyList = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
if (!loginKeyList.isEmpty()) { if (!loginKeyList.isEmpty()) {
// 获取有效登录信息 // 获取有效登录信息
List<LoginTokenDTO> loginTokenInfoList = redisTemplate.opsForValue() List<LoginTokenDTO> loginTokenInfoList = RedisStrings.getList(loginKeyList)
.multiGet(loginKeyList)
.stream() .stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(s -> JSON.parseObject(s, LoginTokenDTO.class)) .map(s -> JSON.parseObject(s, LoginTokenDTO.class))
@@ -407,47 +412,45 @@ public class AuthenticationServiceImpl implements AuthenticationService {
.collect(Collectors.toList()); .collect(Collectors.toList());
// 修改登录信息 // 修改登录信息
for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) { for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) {
String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getLoginTime()); String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getTimestamp());
loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus()); loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus());
loginTokenInfo.setOverride(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent)); loginTokenInfo.setOverride(identity);
RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo); RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo);
} }
} }
// 删除续签信息 // 删除续签信息
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) { if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*")); RedisStrings.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
} }
} }
/** /**
* 生成 loginToken * 生成 loginToken
* *
* @param user user * @param user user
* @param loginTime loginTime * @param identity identity
* @param remoteAddr remoteAddr
* @param location location
* @param userAgent userAgent
* @return loginToken * @return loginToken
*/ */
private String generatorLoginToken(SystemUserDO user, long loginTime, private String generatorLoginToken(SystemUserDO user, RequestIdentityModel identity) {
String remoteAddr, String location, String userAgent) {
Long id = user.getId(); Long id = user.getId();
Long timestamp = identity.getTimestamp();
// 生成 loginToken // 生成 loginToken
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTime); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, timestamp);
LoginTokenDTO loginValue = LoginTokenDTO.builder() LoginTokenDTO loginValue = LoginTokenDTO.builder()
.id(id) .id(id)
.username(user.getUsername())
.status(LoginTokenStatusEnum.OK.getStatus()) .status(LoginTokenStatusEnum.OK.getStatus())
.refreshCount(0) .refreshCount(0)
.origin(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent)) .origin(new RequestIdentityModel(timestamp, identity.getAddress(), identity.getLocation(), identity.getUserAgent()))
.build(); .build();
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue); RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue);
// 生成 refreshToken // 生成 refreshToken
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) { if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime); String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, timestamp);
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue); RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue);
} }
// 返回token // 返回token
return AesEncryptUtils.encryptBase62(id + ":" + loginTime); return AesEncryptUtils.encryptBase62(id + ":" + timestamp);
} }
/** /**
@@ -459,6 +462,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
UserCacheKeyDefine.LOGIN_TOKEN.setTimeout(loginSessionTime); UserCacheKeyDefine.LOGIN_TOKEN.setTimeout(loginSessionTime);
UserCacheKeyDefine.LOGIN_REFRESH.setTimeout(loginSessionTime + appLoginConfig.getRefreshInterval()); UserCacheKeyDefine.LOGIN_REFRESH.setTimeout(loginSessionTime + appLoginConfig.getRefreshInterval());
} }
Integer loginFailedLockTime = appLoginConfig.getLoginFailedLockTime();
if (loginFailedLockTime != null) {
UserCacheKeyDefine.LOGIN_FAILED.setTimeout(loginFailedLockTime);
}
} }
} }

View File

@@ -130,7 +130,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 用户列表 // 用户列表
UserCacheKeyDefine.USER_LIST.getKey(), UserCacheKeyDefine.USER_LIST.getKey(),
// 登录失败次数 // 登录失败次数
UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()) UserCacheKeyDefine.LOGIN_FAILED.format(request.getUsername())
); );
return record.getId(); return record.getId();
} }
@@ -179,7 +179,7 @@ public class SystemUserServiceImpl implements SystemUserService {
log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord)); log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 改为启用则删除登录失败次数缓存 // 改为启用则删除登录失败次数缓存
if (UserStatusEnum.ENABLED.equals(status)) { if (UserStatusEnum.ENABLED.equals(status)) {
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername())); RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED.format(record.getUsername()));
} }
// 更新用户缓存中的 status // 更新用户缓存中的 status
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> { RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
@@ -320,7 +320,7 @@ public class SystemUserServiceImpl implements SystemUserService {
int effect = systemUserDAO.updateById(update); int effect = systemUserDAO.updateById(update);
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect); log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
// 删除登录失败次数缓存 // 删除登录失败次数缓存
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername())); RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED.format(record.getUsername()));
// 删除登录缓存 // 删除登录缓存
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*")); RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
// 删除续签信息 // 删除续签信息
@@ -375,7 +375,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 用户信息缓存 // 用户信息缓存
deleteKeys.add(UserCacheKeyDefine.USER_INFO.format(id)); deleteKeys.add(UserCacheKeyDefine.USER_INFO.format(id));
// 登录失败次数 // 登录失败次数
deleteKeys.add(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(s.getUsername())); deleteKeys.add(UserCacheKeyDefine.LOGIN_FAILED.format(s.getUsername()));
// 登录 token // 登录 token
deleteKeys.addAll(RedisUtils.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"))); deleteKeys.addAll(RedisUtils.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*")));
// 刷新 token // 刷新 token