【Spring Security系列】5 次密码错误触发账号锁定?Spring Security 高效实现方案详解
作者:后端小肥肠
🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案
🍊 有疑问可私信或评论区联系我。
🥑 创作不易未经允许严禁转载。
姊妹篇:
【Spring Security系列】10分钟实现 SpringSecurity + CAS 完美单点登录方案_spring-security-cas-CSDN博客
【Spring Security系列】如何用Spring Security集成手机验证码登录?五分钟搞定!_springsecurity短信验证码登录-CSDN博客
【Spring Security系列】基于Spring Security实现权限动态分配之菜单-角色分配及动态鉴权实践_spring secrity权限角色动态管理-CSDN博客
【Spring Security系列】基于Spring Security实现权限动态分配之用户-角色分配_spring security 角色-CSDN博客
【Spring Security系列】权限之旅:SpringSecurity小程序登录深度探索_spring security 微信小程序登录-CSDN博客
【Spring Security系列】Spring Security+JWT+Redis实现用户认证登录及登出_spring security jwt 退出登录-CSDN博客
目录
1. 前言
2. 技术流程详解
2.1. 技术流程讲解
2.2. 涉及到的SpringSecurity核心组件
3. 技术实现
3.1. 表结构设计
3.2. 核心代码
4. 结语
1. 前言
在现代互联网应用中,账号安全是重中之重。然而,暴力破解攻击依然是最常见的安全威胁之一。攻击者通过自动化脚本尝试大量的用户名和密码组合,试图找到漏洞进入系统。尽管我们可以通过复杂密码要求、验证码、双因子认证等方式来提高安全性,但这些方法无法完全杜绝暴力破解的风险。
为了解决这一问题,账号锁定机制被广泛应用。通过限制用户的密码输入错误次数,当达到一定次数时锁定账号,可以有效防范暴力破解攻击,提高系统安全性。
以下是引入账号锁定机制的几大原因:
- 防止暴力破解攻击:攻击者短时间内多次尝试密码时,锁定机制能阻断其进一步操作。
- 保护用户资产和隐私:避免账号被盗造成的财产损失和隐私泄露。
- 提升系统安全性:通过限制无效登录尝试次数,降低因密码弱点引发的安全隐患。
- 应对安全合规要求:许多行业标准(如 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 {@AutowiredISysUserService userService;@Overridepublic 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;//取authenticationBeanMap<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 {@AutowiredSysUserServiceImpl sysUserService;@Overridepublic void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {// 从请求体中解析 JSON,获取 usernameString 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 实现密码错误多次后自动锁定账号的功能。从需求分析到技术流程,再到核心代码实现,我们完整梳理了账号锁定机制的设计与落地方法。
账号锁定机制是提升系统安全性的重要手段,能够有效防范暴力破解攻击。在实际应用中,还可以结合具体需求进行优化,例如添加解锁通知或动态调整锁定策略等。
希望本文对您有所帮助,若有疑问或建议,欢迎交流!