登录接口逻辑

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;
    }