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=* 必须填写
 | 
					not.null=* 必须填写
 | 
				
			||||||
user.jcaptcha.error=验证码错误
 | 
					user.jcaptcha.error=验证码错误
 | 
				
			||||||
user.jcaptcha.expire=验证码已失效
 | 
					user.jcaptcha.expire=验证码已失效
 | 
				
			||||||
user.not.exists=用户不存在/密码错误
 | 
					user.not.exists=对不起, 您的账号:{0} 不存在.
 | 
				
			||||||
user.password.not.match=用户不存在/密码错误
 | 
					user.password.not.match=用户不存在/密码错误
 | 
				
			||||||
user.password.retry.limit.count=密码输入错误{0}次
 | 
					user.password.retry.limit.count=密码输入错误{0}次
 | 
				
			||||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
 | 
					user.password.retry.limit.exceed=密码错误次数过多,帐户锁定{0}分钟
 | 
				
			||||||
user.password.delete=对不起,您的账号已被删除
 | 
					user.password.delete=对不起,您的账号:{0} 已被删除
 | 
				
			||||||
user.blocked=用户已封禁,请联系管理员
 | 
					user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 | 
				
			||||||
role.blocked=角色已封禁,请联系管理员
 | 
					role.blocked=角色已封禁,请联系管理员
 | 
				
			||||||
user.logout.success=退出成功
 | 
					user.logout.success=退出成功
 | 
				
			||||||
length.not.valid=长度必须在{min}到{max}个字符之间
 | 
					length.not.valid=长度必须在{min}到{max}个字符之间
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,12 @@
 | 
				
			|||||||
not.null=* Required fill in
 | 
					not.null=* Required fill in
 | 
				
			||||||
user.jcaptcha.error=Captcha error
 | 
					user.jcaptcha.error=Captcha error
 | 
				
			||||||
user.jcaptcha.expire=Captcha invalid
 | 
					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.not.match=User does not exist/Password error
 | 
				
			||||||
user.password.retry.limit.count=Password input error {0} times
 | 
					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.retry.limit.exceed=Too many password errors, account locked for {0} minutes
 | 
				
			||||||
user.password.delete=Sorry, your account has been deleted
 | 
					user.password.delete=Sorry, your account:{0} has been deleted
 | 
				
			||||||
user.blocked=User disabled,please contact administrators
 | 
					user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
 | 
				
			||||||
role.blocked=Role disabled,please contact administrators
 | 
					role.blocked=Role disabled,please contact administrators
 | 
				
			||||||
user.logout.success=Exit successful
 | 
					user.logout.success=Exit successful
 | 
				
			||||||
length.not.valid=The length must be between {min} and {max} characters
 | 
					length.not.valid=The length must be between {min} and {max} characters
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,12 @@
 | 
				
			|||||||
not.null=* 必须填写
 | 
					not.null=* 必须填写
 | 
				
			||||||
user.jcaptcha.error=验证码错误
 | 
					user.jcaptcha.error=验证码错误
 | 
				
			||||||
user.jcaptcha.expire=验证码已失效
 | 
					user.jcaptcha.expire=验证码已失效
 | 
				
			||||||
user.not.exists=用户不存在/密码错误
 | 
					user.not.exists=对不起, 您的账号:{0} 不存在.
 | 
				
			||||||
user.password.not.match=用户不存在/密码错误
 | 
					user.password.not.match=用户不存在/密码错误
 | 
				
			||||||
user.password.retry.limit.count=密码输入错误{0}次
 | 
					user.password.retry.limit.count=密码输入错误{0}次
 | 
				
			||||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
 | 
					user.password.retry.limit.exceed=密码错误次数过多,帐户锁定{0}分钟
 | 
				
			||||||
user.password.delete=对不起,您的账号已被删除
 | 
					user.password.delete=对不起,您的账号:{0} 已被删除
 | 
				
			||||||
user.blocked=用户已封禁,请联系管理员
 | 
					user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
 | 
				
			||||||
role.blocked=角色已封禁,请联系管理员
 | 
					role.blocked=角色已封禁,请联系管理员
 | 
				
			||||||
user.logout.success=退出成功
 | 
					user.logout.success=退出成功
 | 
				
			||||||
length.not.valid=长度必须在{min}到{max}个字符之间
 | 
					length.not.valid=长度必须在{min}到{max}个字符之间
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,21 @@ public interface Constants {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    Integer CAPTCHA_EXPIRATION = 2;
 | 
					    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 {
 | 
					public class UserException extends BaseException {
 | 
				
			||||||
    private static final long serialVersionUID = 1L;
 | 
					    private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public UserException(String code, Object[] args) {
 | 
					    public UserException(String code, Object... args) {
 | 
				
			||||||
        super("user", code, args, null);
 | 
					        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.LogininforService;
 | 
				
			||||||
import com.ruoyi.common.core.service.TokenService;
 | 
					import com.ruoyi.common.core.service.TokenService;
 | 
				
			||||||
import com.ruoyi.common.utils.JsonUtils;
 | 
					import com.ruoyi.common.utils.JsonUtils;
 | 
				
			||||||
 | 
					import com.ruoyi.common.utils.MessageUtils;
 | 
				
			||||||
import com.ruoyi.common.utils.ServletUtils;
 | 
					import com.ruoyi.common.utils.ServletUtils;
 | 
				
			||||||
import com.ruoyi.common.utils.StringUtils;
 | 
					import com.ruoyi.common.utils.StringUtils;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					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)
 | 
						public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
				
			||||||
		throws IOException, ServletException {
 | 
							throws IOException, ServletException {
 | 
				
			||||||
		LoginUser loginUser = tokenService.getLoginUser(request);
 | 
							LoginUser loginUser = tokenService.getLoginUser(request);
 | 
				
			||||||
		if (StringUtils.isNotNull(loginUser)) {
 | 
					        String message = MessageUtils.message("user.logout.success");
 | 
				
			||||||
 | 
					        if (StringUtils.isNotNull(loginUser)) {
 | 
				
			||||||
			String userName = loginUser.getUsername();
 | 
								String userName = loginUser.getUsername();
 | 
				
			||||||
			// 删除用户缓存记录
 | 
								// 删除用户缓存记录
 | 
				
			||||||
			tokenService.delLoginUser(loginUser.getToken());
 | 
								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;
 | 
					package com.ruoyi.system.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.util.ObjectUtil;
 | 
				
			||||||
import com.ruoyi.common.constant.Constants;
 | 
					import com.ruoyi.common.constant.Constants;
 | 
				
			||||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
					import com.ruoyi.common.core.domain.entity.SysUser;
 | 
				
			||||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
					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.ServiceException;
 | 
				
			||||||
import com.ruoyi.common.exception.user.CaptchaException;
 | 
					import com.ruoyi.common.exception.user.CaptchaException;
 | 
				
			||||||
import com.ruoyi.common.exception.user.CaptchaExpireException;
 | 
					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.DateUtils;
 | 
				
			||||||
import com.ruoyi.common.utils.MessageUtils;
 | 
					import com.ruoyi.common.utils.MessageUtils;
 | 
				
			||||||
import com.ruoyi.common.utils.RedisUtils;
 | 
					import com.ruoyi.common.utils.RedisUtils;
 | 
				
			||||||
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Resource;
 | 
					import javax.annotation.Resource;
 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 登录校验方法
 | 
					 * 登录校验方法
 | 
				
			||||||
@@ -62,6 +64,13 @@ public class SysLoginService {
 | 
				
			|||||||
        if (captchaOnOff) {
 | 
					        if (captchaOnOff) {
 | 
				
			||||||
            validateCaptcha(username, code, uuid, request);
 | 
					            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;
 | 
					        Authentication authentication = null;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
@@ -70,13 +79,26 @@ public class SysLoginService {
 | 
				
			|||||||
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
 | 
					                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            if (e instanceof BadCredentialsException) {
 | 
					            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 {
 | 
					            } else {
 | 
				
			||||||
                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
 | 
					                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
 | 
				
			||||||
                throw new ServiceException(e.getMessage());
 | 
					                throw new ServiceException(e.getMessage());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // 登录成功 清空错误次数
 | 
				
			||||||
 | 
					        RedisUtils.deleteObject(Constants.LOGIN_ERROR + username);
 | 
				
			||||||
        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
 | 
					        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
 | 
				
			||||||
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
 | 
					        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
 | 
				
			||||||
        recordLoginInfo(loginUser.getUserId(), username);
 | 
					        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.entity.SysUser;
 | 
				
			||||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
					import com.ruoyi.common.core.domain.model.LoginUser;
 | 
				
			||||||
import com.ruoyi.common.enums.UserStatus;
 | 
					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.common.utils.StringUtils;
 | 
				
			||||||
import com.ruoyi.system.service.ISysUserService;
 | 
					import com.ruoyi.system.service.ISysUserService;
 | 
				
			||||||
import com.ruoyi.system.service.SysPermissionService;
 | 
					import com.ruoyi.system.service.SysPermissionService;
 | 
				
			||||||
@@ -34,13 +34,13 @@ public class UserDetailsServiceImpl implements UserDetailsService {
 | 
				
			|||||||
        SysUser user = userService.selectUserByUserName(username);
 | 
					        SysUser user = userService.selectUserByUserName(username);
 | 
				
			||||||
        if (StringUtils.isNull(user)) {
 | 
					        if (StringUtils.isNull(user)) {
 | 
				
			||||||
            log.info("登录用户:{} 不存在.", username);
 | 
					            log.info("登录用户:{} 不存在.", username);
 | 
				
			||||||
            throw new ServiceException("登录用户:" + username + " 不存在");
 | 
					            throw new UserException("user.not.exists", username);
 | 
				
			||||||
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
 | 
					        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
 | 
				
			||||||
            log.info("登录用户:{} 已被删除.", username);
 | 
					            log.info("登录用户:{} 已被删除.", username);
 | 
				
			||||||
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
 | 
					            throw new UserException("user.password.delete", username);
 | 
				
			||||||
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
 | 
					        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
 | 
				
			||||||
            log.info("登录用户:{} 已被停用.", username);
 | 
					            log.info("登录用户:{} 已被停用.", username);
 | 
				
			||||||
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
 | 
					            throw new UserException("user.blocked", username);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return createLoginUser(user);
 | 
					        return createLoginUser(user);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user