Spring Security实现5次密码错误触发账号自动锁定功能

 更新时间:2024年12月23日 09:23:20   作者:后端小肥肠  
在现代互联网应用中,账号安全是重中之重,然而,暴力 破解攻击依然是最常见的安全威胁之一,攻击者通过自动化脚本尝试大量的用户名和密码组合,试图找到漏洞进入系统,所以为了解决这一问题,账号锁定机制被广泛应用,本文介绍了Spring Security实现5次密码错误触发账号锁定功能

1. 前言 

在现代互联网应用中,账号安全是重中之重。然而,暴力 破解攻击依然是最常见的安全威胁之一。攻击者通过自动化脚本尝试大量的用户名和密码组合,试图找到漏洞进入系统。尽管我们可以通过复杂密码要求、验证码、双因子认证等方式来提高安全性,但这些方法无法完全杜绝暴力 破解的风险。

为了解决这一问题,账号锁定机制被广泛应用。通过限制用户的密码输入错误次数,当达到一定次数时锁定账号,可以有效防范暴力 破解攻击,提高系统安全性。

以下是引入账号锁定机制的几大原因:

  1. 防止暴力 破解攻击:攻击者短时间内多次尝试密码时,锁定机制能阻断其进一步操作。
  2. 保护用户资产和隐私:避免账号被盗造成的财产损失和隐私泄露。
  3. 提升系统安全性:通过限制无效登录尝试次数,降低因密码弱点引发的安全隐患。
  4. 应对安全合规要求:许多行业标准(如 GDPR、ISO27001)建议对异常登录行为采取保护措施。

在本文中,我们将通过 Spring Security 来实现一个简单而高效的账号锁定机制。当用户密码连续输入错误达到 5 次后,系统将自动锁定该账号3分钟。接下来,我们将通过技术流程、表结构设计、核心代码实现以及效果测试,详细讲解如何在 Spring Security 中落地这一功能。

2. 技术流程详解

2.1.  技术流程讲解

基于SpringSecurity实现密码错误锁定账号的流程如下:

其实光看流程图已经能很清晰地理解完整流程,我这边还是用文字再给大家梳理一遍:

1. 用户提交登录请求

用户输入用户名和密码后发起登录请求,系统开始验证其身份。

2. 加载用户信息

Spring Security 使用自定义的 UserService 加载用户信息,包括用户名、密码、锁定状态(accountNonLocked)、登录失败次数(loginAttempts)、锁定时间(lockTime)等。

3. 检查账号锁定状态

  • 如果账号被锁定:
    • 判断锁定时间是否已过:
      • 锁定时间未过:抛出 LockedException,阻止登录。
      • 锁定时间已过:解锁账号(accountNonLocked = true)、重置登录失败次数(loginAttempts = 0),并允许继续登录。
  • 如果账号未锁定,直接进入密码验证。

4. 验证密码

  • 密码正确
    • 登录成功,重置用户状态(失败次数清零,锁定状态解除),并更新到数据库。
  • 密码错误
    • 增加登录失败次数(loginAttempts + 1)。
    • 如果失败次数达到 5 次或以上,锁定账号(accountNonLocked = false),并记录当前锁定时间(lockTime = 当前时间)。

5. 更新用户状态

  • 登录成功时:更新用户信息到数据库,并执行登录成功后的业务逻辑。
  • 登录失败时:更新用户的失败次数和锁定状态到数据库,并执行失败后的处理逻辑。

6. 等待下次登录尝试

用户根据账号状态和锁定时间,决定何时再次发起登录请求。

2.2. 涉及到的SpringSecurity核心组件

以上流程涉及到的SpringSecurity核心组件如下:

1. AuthenticationManager

  • 管理认证流程,调用 AuthenticationProvider 验证用户凭据。

2. AuthenticationProvider

  • 执行具体的认证逻辑,包括密码验证和账号状态检查。

3. PasswordEncoder

  • 用于密码加密和验证,例如使用 BCryptPasswordEncoder

4. AuthenticationSuccessHandler

  • 处理登录成功后的逻辑,例如重置失败登录次数和解锁账号。

5. AuthenticationFailureHandler

  • 处理登录失败后的逻辑,例如增加登录失败次数和锁定账号。

6. AuthenticationEntryPoint

  • 处理未认证用户访问受保护资源时的逻辑,返回错误信息。

7. SecurityContextHolder

  • 存储和提供当前用户的认证信息(Authentication 对象)。

8. ExceptionTranslationFilter

  • 捕获认证过程中抛出的异常,并交给 AuthenticationEntryPoint 或 AuthenticationFailureHandler 处理。

9. UsernamePasswordAuthenticationFilter

  • 处理基于用户名和密码的登录请求,触发认证流程。

10.HttpSecurity

  • 配置认证流程和组件,包括认证、授权和异常处理。

这些组件共同协作,实现 Spring Security 的认证和密码错误锁定账号功能。

3. 技术实现

3.1. 表结构设计

密码错误账号锁定只涉及到user表,这是我的表结构,你可以根据你自己的灵活调整:

CREATE TABLE user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    login_attempts INT DEFAULT 0, -- 登录失败次数
    lock_time TIMESTAMP NULL, -- 账号锁定时间
    account_non_locked BOOLEAN DEFAULT TRUE -- 账号是否锁定
);

3.2. 核心代码

1. 编写自定义UsernamePasswordAuthenticationFilter

public class SecurUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
 
    @Autowired
    ISysUserService userService;
 
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
 
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
 
            ObjectMapper mapper = new ObjectMapper();
            UsernamePasswordAuthenticationToken authRequest = null;
            //取authenticationBean
            Map<String, String> authenticationBean = null;
            //用try with resource,方便自动释放资源
            try (InputStream is = request.getInputStream()) {
                authenticationBean = mapper.readValue(is, Map.class);
            } catch (IOException e) {
                //将异常放到自定义的异常类中
                throw  new SecurAuthenticationException(e.getMessage());
            }
            try {
                if (!authenticationBean.isEmpty()) {
                    //获得账号、密码
                    String username = authenticationBean.get(SPRING_SECURITY_FORM_USERNAME_KEY);
                    String password = authenticationBean.get(SPRING_SECURITY_FORM_PASSWORD_KEY);
 
                    request.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);
 
                    //检测账号、密码是否存在
                    if (userService.checkLogin(username, password)) {
                        //将账号、密码装入UsernamePasswordAuthenticationToken中
                        authRequest = new UsernamePasswordAuthenticationToken(username, password);
                        setDetails(request,authRequest );
                        return this.getAuthenticationManager().authenticate(authRequest);
                    }
 
                }
            } catch (Exception e) {
                throw new SecurAuthenticationException(e.getMessage());
            }
            return null;
        } else {
            return this.attemptAuthentication(request, response);
        }
    }
}

这段代码是一个自定义的 Spring Security 登录过滤器,用于处理 JSON 格式的登录请求(代替默认的表单登录)。它通过解析请求体中的 JSON,提取用户名和密码,并调用用户服务检查登录信息是否正确。如果验证通过,则创建并返回一个 UsernamePasswordAuthenticationToken,交由认证管理器执行进一步的认证流程;否则,抛出自定义异常终止认证。 

2. 编写userService.checkLogin方法 

    public boolean checkLogin(String username, String rawPassword) throws Exception {
        SysUser userEntity = this.getUserByUserName(username);
        System.out.println("userEntity = " + userEntity);
        if (userEntity == null) {
            //System.out.println("checkLogin--------->账号不存在,请重新尝试!");
            //设置友好提示
            throw new Exception("账号不存在,请重新尝试!");
        } else {
            // 检查账号锁定状态
            handleAccountLock(userEntity);
            //加密的密码
            String encodedPassword = userEntity.getPassword();
            //和加密后的密码进行比配
            if (!passwordEncoder.matches(rawPassword, encodedPassword)) {
                //System.out.println("checkLogin--------->密码不正确!");
                //设置友好提示
                throw new Exception("密码不正确!");
            } else {
                return true;
            }
        }
    }

这段代码实现了用户登录验证逻辑,首先通过用户名获取用户信息,如果用户不存在则抛出异常提示账号不存在。接着检查用户账号的锁定状态(调用 handleAccountLock 方法),如果账号未锁定,则验证用户输入的原始密码是否与加密存储的密码匹配。密码匹配成功则返回 true,否则抛出异常提示密码不正确。

3.  编写 handleAccountLock方法

    /**
     * 检查并处理账号锁定逻辑
     */
    private void handleAccountLock(SysUser user) {
        if (!user.getAccountNonLocked() && user.getLockTime() != null) {
            // 当前时间
            Date now = new Date();
 
            // 解锁时间
            Date unlockTime = new Date(user.getLockTime().getTime() + LOCK_DURATION.toMillis());
 
            if (now.before(unlockTime)) {
                throw new LockedException("账号已锁定,请3分钟后再试");
            }
 
            // 解锁账号并重置状态
            user.setAccountNonLocked(true);
            user.setLoginAttempts(0);
            user.setLockTime(null);
            this.updateById(user);
        }
    }

这段代码实现了账号锁定状态的检查与处理逻辑。如果用户账号已锁定且存在锁定时间,系统会计算解锁时间。如果当前时间在解锁时间之前,则抛出异常提示账号被锁定;如果超过了解锁时间,则解锁账号,同时重置失败次数和锁定时间,并更新用户信息到数据库。 

 4. 编写increaseFailedAttempts,用于用户登录失败时调用

    public void increaseFailedAttempts(SysUser user) {
        int attempts = user.getLoginAttempts() + 1;
        if (attempts >= MAX_LOGIN_ATTEMPTS) {
            user.setAccountNonLocked(false);
            user.setLockTime(new Date());
        }
        user.setLoginAttempts(attempts);
        this.updateById(user);
    }

这段代码实现了增加用户登录失败次数的逻辑。当用户登录失败时,登录失败次数 (loginAttempts) 增加 1;如果失败次数达到或超过最大允许次数 (MAX_LOGIN_ATTEMPTS),系统会将用户账号设置为锁定状态 (accountNonLocked=false) 并记录锁定时间 (lockTime)。最后,将更新后的用户状态保存到数据库。 

5. 编写resetLoginAttempts,用于用户登录成功时调用

    public void resetLoginAttempts(String username) {
        SysUser user = this.getUserByUserName(username);
        if(user==null){
            throw new UsernameNotFoundException(String.format("%s.这个用户不存在或已被禁用", username));
        }
        user.setLoginAttempts(0);
        user.setAccountNonLocked(true);
        user.setLockTime(null);
        this.updateById(user);
 
    }

这段代码实现了重置用户登录失败次数的逻辑。通过用户名查询用户信息,如果用户不存在则抛出异常。若用户存在,则将其登录失败次数 (loginAttempts) 重置为 0,解锁账号 (accountNonLocked=true),并清除锁定时间 (lockTime=null)。最后,将更新后的用户信息保存到数据库。 

6. 编写自定义AuthenticationSuccessHandler,在用户登录成功时调用resetLoginAttempts

7. 编写自定义AuthenticationFailureHandler

@Component
public class SecurAuthenticationFailureHandler extends JSONAuthentication implements AuthenticationFailureHandler {
    @Autowired
    SysUserServiceImpl sysUserService;
 
 
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException e) throws IOException, ServletException {
 
        // 从请求体中解析 JSON,获取 username
        String username = (String) request.getAttribute(SPRING_SECURITY_FORM_USERNAME_KEY);
        // 登录失败时增加失败次数
        if (username != null) {
            SysUser user = sysUserService.getUserByUserName(username);
            if (user != null) {
                sysUserService.increaseFailedAttempts(user);
            }
        }
 
        ResponseStructure data = ResponseStructure.instance(ALL_RETURN_401.getCode(),"登录失败:"+e.getMessage());
        //输出
        this.WriteJSON(request, response, data);
    }
}

这段代码是一个自定义的登录失败处理器 SecurAuthenticationFailureHandler,在用户登录失败时被触发。它通过解析请求获取登录失败的用户名,并调用服务方法 increaseFailedAttempts 增加用户的登录失败次数。同时,根据异常信息生成统一的响应结构,并以 JSON 格式返回错误信息给客户端,提示登录失败的原因。 

4. 结语

本文详细讲解了如何通过 Spring Security 实现密码错误多次后自动锁定账号的功能。从需求分析到技术流程,再到核心代码实现,我们完整梳理了账号锁定机制的设计与落地方法。

账号锁定机制是提升系统安全性的重要手段,能够有效防范暴力 破解攻击。在实际应用中,还可以结合具体需求进行优化,例如添加解锁通知或动态调整锁定策略等。

以上就是Spring Security实现5次密码错误触发账号自动锁定功能的详细内容,更多关于Spring Security密码错误账号锁定的资料请关注脚本之家其它相关文章!

相关文章

  • 如何修改logback.xml配置文件在resource以外的位置

    如何修改logback.xml配置文件在resource以外的位置

    这篇文章主要介绍了如何修改logback.xml配置文件在resource以外的位置,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • J2EE中的struts2表单细节处理

    J2EE中的struts2表单细节处理

    这篇文章主要介绍了J2EE中的struts2表单细节处理的相关资料,需要的朋友可以参考下
    2017-06-06
  • java map中相同的key保存多个value值方式

    java map中相同的key保存多个value值方式

    这篇文章主要介绍了java map中相同的key保存多个value值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 浅谈JavaIO之try with底层原理

    浅谈JavaIO之try with底层原理

    众所周知,所有被打开的系统资源,比如流、文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重大的生产事故。本文将介绍JavaIO之try with底层原理。
    2021-06-06
  • 开源的Java图片处理库实例详解

    开源的Java图片处理库实例详解

    Java 图片处理库提供了丰富的功能,用于处理和增强图像,在Java生态系统中,有几个流行的开源库可以用于图片处理,这些库提供了丰富的功能,如图像缩放、裁剪、颜色调整、格式转换等,本文介绍开源的Java图片处理库介绍,感兴趣的朋友一起看看吧
    2024-03-03
  • SpringBoot实现定时发送邮件的三种方法案例详解

    SpringBoot实现定时发送邮件的三种方法案例详解

    这篇文章主要介绍了SpringBoot三种方法实现定时发送邮件的案例,Spring框架的定时任务调度功能支持配置和注解两种方式Spring Boot在Spring框架的基础上实现了继承,并对其中基于注解方式的定时任务实现了非常好的支持,本文给大家详细讲解,需要的朋友可以参考下
    2023-03-03
  • 解决SpringBoot文件上传临时目录找不到的问题

    解决SpringBoot文件上传临时目录找不到的问题

    这篇文章主要介绍了解决SpringBoot文件上传临时目录找不到的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • IntelliJ IDEA(或者JetBrains PyCharm)中弹出

    IntelliJ IDEA(或者JetBrains PyCharm)中弹出"IntelliJ IDEA License

    今天小编就为大家分享一篇关于IntelliJ IDEA(或者JetBrains PyCharm)中弹出"IntelliJ IDEA License Activation"的解决办法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • java与scala数组及集合的基本操作对比

    java与scala数组及集合的基本操作对比

    这篇文章主要介绍了java与scala数组及集合的基本操作对比,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 使用spring security明文密码校验时报错-BadCredentialsException: Bad credentials的问题

    使用spring security明文密码校验时报错-BadCredentialsException:&nbs

    小编遇到这样一个问题在学习spring security时使用明文密码进行登录校验时报错"org.springframework.security.authentication.BadCredentialsException: Bad credentials,今天给大家分享问题原因及解决方案,感兴趣的朋友一起看看吧
    2023-10-10

最新评论