mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	update 用户登录 支持校验错误次数锁定登录
This commit is contained in:
		@@ -2,12 +2,12 @@
 | 
			
		||||
not.null=* 必须填写
 | 
			
		||||
user.jcaptcha.error=验证码错误
 | 
			
		||||
user.jcaptcha.expire=验证码已失效
 | 
			
		||||
user.not.exists=用户不存在/密码错误
 | 
			
		||||
user.not.exists=对不起, 您的账号:{0} 不存在.
 | 
			
		||||
user.password.not.match=用户不存在/密码错误
 | 
			
		||||
user.password.retry.limit.count=密码输入错误{0}次
 | 
			
		||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
 | 
			
		||||
user.password.delete=对不起,您的账号已被删除
 | 
			
		||||
user.blocked=用户已封禁,请联系管理员
 | 
			
		||||
user.password.retry.limit.exceed=密码错误次数过多,帐户锁定{0}分钟
 | 
			
		||||
user.password.delete=对不起,您的账号:{0} 已被删除
 | 
			
		||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 | 
			
		||||
role.blocked=角色已封禁,请联系管理员
 | 
			
		||||
user.logout.success=退出成功
 | 
			
		||||
length.not.valid=长度必须在{min}到{max}个字符之间
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,12 @@
 | 
			
		||||
not.null=* Required fill in
 | 
			
		||||
user.jcaptcha.error=Captcha error
 | 
			
		||||
user.jcaptcha.expire=Captcha invalid
 | 
			
		||||
user.not.exists=User does not exist/Password error
 | 
			
		||||
user.not.exists=Sorry, your account: {0} does not exist
 | 
			
		||||
user.password.not.match=User does not exist/Password error
 | 
			
		||||
user.password.retry.limit.count=Password input error {0} times
 | 
			
		||||
user.password.retry.limit.exceed=Password input error {0} times, account locked for 10 minutes
 | 
			
		||||
user.password.delete=Sorry, your account has been deleted
 | 
			
		||||
user.blocked=User disabled,please contact administrators
 | 
			
		||||
user.password.retry.limit.exceed=Too many password errors, account locked for {0} minutes
 | 
			
		||||
user.password.delete=Sorry, your account:{0} has been deleted
 | 
			
		||||
user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
 | 
			
		||||
role.blocked=Role disabled,please contact administrators
 | 
			
		||||
user.logout.success=Exit successful
 | 
			
		||||
length.not.valid=The length must be between {min} and {max} characters
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,12 @@
 | 
			
		||||
not.null=* 必须填写
 | 
			
		||||
user.jcaptcha.error=验证码错误
 | 
			
		||||
user.jcaptcha.expire=验证码已失效
 | 
			
		||||
user.not.exists=用户不存在/密码错误
 | 
			
		||||
user.not.exists=对不起, 您的账号:{0} 不存在.
 | 
			
		||||
user.password.not.match=用户不存在/密码错误
 | 
			
		||||
user.password.retry.limit.count=密码输入错误{0}次
 | 
			
		||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
 | 
			
		||||
user.password.delete=对不起,您的账号已被删除
 | 
			
		||||
user.blocked=用户已封禁,请联系管理员
 | 
			
		||||
user.password.retry.limit.exceed=密码错误次数过多,帐户锁定{0}分钟
 | 
			
		||||
user.password.delete=对不起,您的账号:{0} 已被删除
 | 
			
		||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 | 
			
		||||
role.blocked=角色已封禁,请联系管理员
 | 
			
		||||
user.logout.success=退出成功
 | 
			
		||||
length.not.valid=长度必须在{min}到{max}个字符之间
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,21 @@ public interface Constants {
 | 
			
		||||
     */
 | 
			
		||||
    Integer CAPTCHA_EXPIRATION = 2;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登陆错误 redis key
 | 
			
		||||
     */
 | 
			
		||||
    String LOGIN_ERROR = "login_error:";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录错误次数
 | 
			
		||||
     */
 | 
			
		||||
    Integer LOGIN_ERROR_NUMBER = 5;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 登录错误限制时间(分钟)
 | 
			
		||||
     */
 | 
			
		||||
    Integer LOGIN_ERROR_LIMIT_TIME = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 令牌
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import com.ruoyi.common.exception.base.BaseException;
 | 
			
		||||
public class UserException extends BaseException {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    public UserException(String code, Object[] args) {
 | 
			
		||||
    public UserException(String code, Object... args) {
 | 
			
		||||
        super("user", code, args, null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.core.service.LogininforService;
 | 
			
		||||
import com.ruoyi.common.core.service.TokenService;
 | 
			
		||||
import com.ruoyi.common.utils.JsonUtils;
 | 
			
		||||
import com.ruoyi.common.utils.MessageUtils;
 | 
			
		||||
import com.ruoyi.common.utils.ServletUtils;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
@@ -40,14 +41,15 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
 | 
			
		||||
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
			
		||||
		throws IOException, ServletException {
 | 
			
		||||
		LoginUser loginUser = tokenService.getLoginUser(request);
 | 
			
		||||
        String message = MessageUtils.message("user.logout.success");
 | 
			
		||||
        if (StringUtils.isNotNull(loginUser)) {
 | 
			
		||||
			String userName = loginUser.getUsername();
 | 
			
		||||
			// 删除用户缓存记录
 | 
			
		||||
			tokenService.delLoginUser(loginUser.getToken());
 | 
			
		||||
			// 记录用户退出日志
 | 
			
		||||
			asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功", request);
 | 
			
		||||
			asyncService.recordLogininfor(userName, Constants.LOGOUT, message, request);
 | 
			
		||||
		}
 | 
			
		||||
		ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_OK, "退出成功")));
 | 
			
		||||
		ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_OK, message)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package com.ruoyi.system.service;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
@@ -8,7 +9,7 @@ import com.ruoyi.common.core.service.TokenService;
 | 
			
		||||
import com.ruoyi.common.exception.ServiceException;
 | 
			
		||||
import com.ruoyi.common.exception.user.CaptchaException;
 | 
			
		||||
import com.ruoyi.common.exception.user.CaptchaExpireException;
 | 
			
		||||
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
 | 
			
		||||
import com.ruoyi.common.exception.user.UserException;
 | 
			
		||||
import com.ruoyi.common.utils.DateUtils;
 | 
			
		||||
import com.ruoyi.common.utils.MessageUtils;
 | 
			
		||||
import com.ruoyi.common.utils.RedisUtils;
 | 
			
		||||
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 登录校验方法
 | 
			
		||||
@@ -62,6 +64,13 @@ public class SysLoginService {
 | 
			
		||||
        if (captchaOnOff) {
 | 
			
		||||
            validateCaptcha(username, code, uuid, request);
 | 
			
		||||
        }
 | 
			
		||||
        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
 | 
			
		||||
        Integer errorNumber = RedisUtils.getCacheObject(Constants.LOGIN_ERROR + username);
 | 
			
		||||
        // 锁定时间内登录 则踢出
 | 
			
		||||
        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
 | 
			
		||||
            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
 | 
			
		||||
            throw new UserException("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
 | 
			
		||||
        }
 | 
			
		||||
        // 用户验证
 | 
			
		||||
        Authentication authentication = null;
 | 
			
		||||
        try {
 | 
			
		||||
@@ -70,13 +79,26 @@ public class SysLoginService {
 | 
			
		||||
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            if (e instanceof BadCredentialsException) {
 | 
			
		||||
                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
 | 
			
		||||
                throw new UserPasswordNotMatchException();
 | 
			
		||||
                // 是否第一次
 | 
			
		||||
                errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
 | 
			
		||||
                // 达到规定错误次数 则锁定登录
 | 
			
		||||
                if (errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
 | 
			
		||||
                    RedisUtils.setCacheObject(Constants.LOGIN_ERROR + username, errorNumber, Constants.LOGIN_ERROR_LIMIT_TIME, TimeUnit.MINUTES);
 | 
			
		||||
                    asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
 | 
			
		||||
                    throw new UserException("user.password.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 未达到规定错误次数 则递增
 | 
			
		||||
                    RedisUtils.setCacheObject(Constants.LOGIN_ERROR + username, errorNumber);
 | 
			
		||||
                    asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", errorNumber), request);
 | 
			
		||||
                    throw new UserException("user.password.retry.limit.count", errorNumber);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
 | 
			
		||||
                throw new ServiceException(e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 登录成功 清空错误次数
 | 
			
		||||
        RedisUtils.deleteObject(Constants.LOGIN_ERROR + username);
 | 
			
		||||
        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
 | 
			
		||||
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
 | 
			
		||||
        recordLoginInfo(loginUser.getUserId(), username);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package com.ruoyi.system.service.impl;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.enums.UserStatus;
 | 
			
		||||
import com.ruoyi.common.exception.ServiceException;
 | 
			
		||||
import com.ruoyi.common.exception.user.UserException;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import com.ruoyi.system.service.ISysUserService;
 | 
			
		||||
import com.ruoyi.system.service.SysPermissionService;
 | 
			
		||||
@@ -34,13 +34,13 @@ public class UserDetailsServiceImpl implements UserDetailsService {
 | 
			
		||||
        SysUser user = userService.selectUserByUserName(username);
 | 
			
		||||
        if (StringUtils.isNull(user)) {
 | 
			
		||||
            log.info("登录用户:{} 不存在.", username);
 | 
			
		||||
            throw new ServiceException("登录用户:" + username + " 不存在");
 | 
			
		||||
            throw new UserException("user.not.exists", username);
 | 
			
		||||
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
 | 
			
		||||
            log.info("登录用户:{} 已被删除.", username);
 | 
			
		||||
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
 | 
			
		||||
            throw new UserException("user.password.delete", username);
 | 
			
		||||
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
 | 
			
		||||
            log.info("登录用户:{} 已被停用.", username);
 | 
			
		||||
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
 | 
			
		||||
            throw new UserException("user.blocked", username);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return createLoginUser(user);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user