Spring Security 如何防止暴力破解
在构建安全的 Web 应用时,防御暴力破解攻击是一个关键的考虑因素。Spring Security 提供了一套灵活的机制来帮助我们实现这一目标。本文将介绍如何使用 Spring Security 来记录失败的登录尝试,并在尝试次数超过一定阈值时封锁攻击者的 IP 地址。
亦可基于本文的思路实现封锁攻击者的账号。
一 封装登录计数器
@Service
public class LoginAttemptService {
@Autowired
RedisTemplate redisTemplate;
public static final int MAX_ATTEMPT = 10;
@Autowired
HttpServletRequest request;
public void loginFailed() {
Integer attempts = null;
attempts = (Integer) redisTemplate.opsForValue().get(getClientIP());
if (attempts == null) {
attempts = 0;
}
attempts++;
redisTemplate.opsForValue().set(getClientIP(), attempts);
redisTemplate.expire(getClientIP(), 1, TimeUnit.DAYS);
}
public boolean isBlocked() {
Integer attempts = (Integer) redisTemplate.opsForValue().get(getClientIP());
return attempts != null && attempts >= MAX_ATTEMPT;
}
private String getClientIP() {
final String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader != null) {
return xfHeader.split(",")[0];
}
return request.getRemoteAddr();
}
public void loginSuccess() {
redisTemplate.delete(getClientIP());
}
}
这里核心方法就三个:
-
loginFailed:登录失败的时候调用该方法,以当前登录 IP 为 key,在 Redis 上保存登录失败的次数,并设置过期时间为 1 天。 -
isBlocked:判断当前登录 IP 是否已经达到最大重试次数,达到了就要禁用该 IP 地址了(禁止时间为 1 天)。 -
loginSuccess:登录成功后,清除该 IP 地址登录失败的记录。
二 监听器
前面 LoginAttemptService 中的方法,我们分别在登录成功和登录失败的监听器中进行调用。
首先,我们将创建一个 AuthenticationFailureListener
,用于监听认证失败事件,并记录相关 IP 地址的失败尝试次数。
@Component
public class AuthenticationFailureListener implements
ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
@Autowired
private LoginAttemptService loginAttemptService;
@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
loginAttemptService.loginFailed();
}
}
接下来再创建一个登录成功的监听器,用来清除当前 IP 登录失败的次数:
@Component
public class AuthenticationSuccessListener implements
ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private LoginAttemptService loginAttemptService;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
loginAttemptService.loginSuccess();
}
}
Spring Security 在登录成功或者失败的时候都会自动发布相关事件,我们这里只需要定义监听器就可以获取到事件,相关源码在 ProviderManager#authenticate 方法中,如下:

三 修改登录逻辑
在自定义的 UserDetailsService
实现中,我们需要在加载用户详情之前检查请求的 IP 地址是否已被封锁。
@Service
public class UserService implements UserDetailsService {
@Autowired
LoginAttemptService loginAttemptService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (loginAttemptService.isBlocked()) {
throw new RuntimeException("blocked");
}
return User.builder().username(username).password("{noop}123").authorities("admin").build();
}
}
四 自定义认证失败处理器
最后,我们将修改 CustomAuthenticationFailureHandler
,以自定义错误消息并通知用户其 IP 已被封锁。
@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private MessageSource messages;
@Autowired
private HttpServletRequest request;
@Autowired
private LoginAttemptService loginAttemptService;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// ... 错误处理逻辑
}
}
五 结论
通过上述步骤,我们已经在 Spring Security 中实现了一个基本的暴力破解防御机制。不过在实际开发中,我们还可以采用更复杂的策略,如结合验证码、多因素认证等。
转发于:江南一点雨
扫码领红包微信赞赏
支付宝扫码领红包
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。侵权投诉:375170667@qq.com