1. 切面逻辑
包含解密、登录失败次数验证、系统操作日志切面
1.1 代码
bash
@PostMapping("/login")
@Operation(summary = "用户登录")
// 系统操作日志切面
@OperateLog(module = "user", operateTypeKey = "log.login.type",
targetValue = "#loginRequest?.username",
operateDescValues = {"#loginRequest?.username", "#ip"})
@LoginAttempt(username = "#loginRequest.username", ip = "#ip") // 判断登录失败次数,给出提示和限制
@Decrypt // 对加密的密码 ,进行解密
public UserInfo login(@RequestBody LoginRequest loginRequest,
@RequestHeader("x-forwarded-for") String ip) {...}
1.2 逻辑
@OperateLog,@LoginAttempt 采用的都是 @Around(...)接入
java
【登录--失败次数判断、日志记录业务流程】
1. loginAttempAspect_before
啥都不做
2. OperateLogAspect_before
组装日志对象;
3. login_api
登录逻辑处理
4. OperateLogAspect_after
捕捉 login_api 异常,记录异常信息;将异常抛出
异步记录操作日志;
5. loginAttempAspect_after
/**
* 1. 查询用户登录失败次数,如果登录错误次数大于限值
* 登录次数大于限制,计算下次登录等待的剩余时间,异常抛出;直接结束
* 如果时间到了,将登录错误次数设置为0
* 2.捕捉 OperateLogAspect_after 异常
* 如果异常原因是 用户名和密码错误
* 首次错误,开始记录失败时间
* 用户错误的次数+1
* 计算剩余登录次数,
* 大于0,抛出剩余登录次数;
* 小于等于0,说明失败次数过多,让用户开始等待下次登录
*/
2. 内部逻辑
采用的 satoken安全认证框架
这个接口的逻辑如下:
bash
4. 用户登录
4.1 使用用户名和加密后的密码登录,
4.2 检查是否存在用户
4.3 查询数据库用户,校验密码
4.4 检查是否被锁定
4.5 生成 SaLoginModel 登录数据,使用 StpUtil.login(user.getUsername(), loginModel); 登录;
4.6 将用户信息放入 SaSession ,如username: StpUtil.getSession().set("username", userInfo.getUsername());
4.7 更新数据库登录时间
4.8 设置用户登录时间,是否过期状态(前端校验、处理)
4.9 返回用户信息
3. 相关代码
controller
java
@PostMapping("/login")
@Operation(summary = "用户登录")
@OperateLog(module = "user", operateTypeKey = "log.login.type",
targetValue = "#loginRequest?.username",
operateDescValues = {"#loginRequest?.username", "#ip"})
@LoginAttempt(username = "#loginRequest.username", ip = "#ip") // 判断登录次数
@Decrypt // 对加密的密码 ,进行解密
public UserInfo login(@RequestBody LoginRequest loginRequest,
@RequestHeader("x-forwarded-for") String ip) {
log.info("x-forwarded-for={}", ip);
try {
// 登录
UserInfo userInfo = sessionService.loginByUsernameAndPassword(
loginRequest.getUsername(), loginRequest.getPassword());
return userInfo;
} catch (Exception e) {
String message = MessageUtils.get("error.auth.invalid_credentials");
if (e instanceof ResponseStatusException) {
ResponseStatusException originException = (ResponseStatusException) e;
message = originException.getReason();
}
log.info("login message={}", message);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message, e);
}
}
service
java
public UserInfo loginByUsernameAndPassword(String username, String password) {
User user = userRepository.findByUsername(username).orElse(null);
// 检查是否存在用户
userService.checkUserExist(user);
// 检查用户名密码
userService.checkUserPassword(user, password);
// 检查是否被锁定
userService.checkUserLocked(user);
// 首次登录,需要修改密码(前端校验)
boolean firstLogin = userService.checkUserFirstLogin(user);
// 检查密码是否过期(前端校验)
boolean pwdExpired = isPasswordExpired(user);
// userService.checkPWDExpired(pwdExpired);
// 在satoken 登录
SaLoginModel loginModel = new SaLoginModel();
loginModel.setExtra("username", user.getUsername());
StpUtil.login(user.getUsername(), loginModel);
// 生成返回消息
UserInfo userInfo = userEntity2UserInfo(user);
userInfo.setFirstLogin(firstLogin);
userInfo.setAvatar(redisStringUtil.getAvatar(userInfo.getUsername()));
// 创建登录模型并设置 extra 扩展参数
loginModel.setExtra("username", userInfo.getUsername());
log.info("[User][Login] username = {}", userInfo.getUsername());
log.info("[User][Login] username = {}", StpUtil.getTokenInfo());
StpUtil.getSession().set("username", userInfo.getUsername());
StpUtil.getSession().set("permissions", userInfo.getPermissionList());
StpUtil.getSession().set("userEntity", user);
// 更新数据库登录时间
userRepository.updateLastLoginTime(userInfo.getUsername(), LocalDateTime.now());
// 设置用户登录时间
userInfo.setLoginAt(LocalDateTime.now());
userInfo.setPasswordExpired(pwdExpired);
StpUtil.getSession().set("userPassedPswExpired", !pwdExpired);
// 返回对象
return userInfo;
}