通过Spring security中的一些配置和编码可以实现。
顺便说一句,我不建议在可疑的无效登录试验前做一些延迟。您可能会延迟响应可疑的登录尝试,但这种延迟会使您在应用程序中暂停线程一段时间。如果您的应用程序同时发生大量无效登录,这可能会对您的系统造成 DoS 或 DDoS 攻击。
更好的方法是对可疑的无效登录做出快速响应,但同时暂停用户尝试登录的用户帐户。这样,暴力攻击避免不会导致提供 Dos 或 DDoS 攻击。
尽管如此,暂停用户帐户也为 DoS 攻击提供了一种途径,因为它可能导致无法向真实用户提供服务。但是,正确的安全方案在这些情况下会有所帮助。
例如,如果检测到暴力攻击,您可以:
- 显示一些验证码,
- 暂停帐户,发送电子邮件或短信帐户所有者更改密码
- 或者,暂停帐户一段时间,同时通知帐户所有者更改密码,
- ...
所有这些都取决于您的域和服务场景。
例如,您可以实现自己的 UserDetailsService 并在此实现中检测暴力攻击。
为了实现 Spring Security 的最后一个场景,下面的代码声明了一个 authentication-manager,它传递了一个自定义的 UserDetailsService,这里的类型是 JdbcDaoImpl(注意,包名和查询必须修改为您自己的包和数据模型)。
<authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="CustomUserDetailsService" />
</authentication-manager>
<!--
This bean is to provide jdbc-user-service.
Also, it provides a way to avoid BFD along with AuthenticationFailureListener
-->
<bean id="CustomUserDetailsService" class="com.example.CustomUserDetailsService">
<property name="dataSource" ref="dataSource" />
<property name="usersByUsernameQuery" value="SELECT user_client.user_name, user_client.password, user.is_active
FROM user_client INNER JOIN user ON user_client.fk_user = user.ID
WHERE user_client.user_name=? "/>
<property name="authoritiesByUsernameQuery" value="SELECT user_client.user_name, CONCAT('ROLE_',user_client.client_id)
FROM user_client WHERE user_client.user_name=? "/>
</bean>
CustomUserDetailsService 检测是否发生了暴力攻击,以及我将很快讨论的 AuthenticationFailureListener。 FailedLoginCacheManager 是一个 ehcache 包装器,用于维护失败的登录(用户名)及其相对失败的数量。缓存设置了适当的 timeToIdleSeconds 以使帐户暂停。
public class CustomUserDetailsService extends JdbcDaoImpl {
@Autowired
private FailedLoginCacheManager cacheManager;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (cacheManager.isBruteForceAttackLogin(username)) {
//throw some security exception
...
}
return super.loadUserByUsername(username);
}
}
此外,还实现了一个 ApplicationListener 来检测失败的登录尝试并将其保存在 ehcache 中。
@Component public class AuthenticationFailureListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
@Autowired
private FailedLoginCacheManager cacheManager;
@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) event.getSource();
String userName = token.getPrincipal().toString();
cacheManager.updateLoginFailureStatus(userName);
}}
关于 FailedLoginCacheManager 服务的更多讨论无需赘述,但此处讨论的两个主要方法可以像以下方法一样实现:
public void updateLoginFailureStatus(String userName) {
Cache cache = manager.getCache(CACHE_NAME);
Element element = cache.get(userName);
if (isValid(element)) {
int failureCount = (Integer)element.getObjectValue();
cache.remove(userName);
cache.put(new Element(userName, ++failureCount));
} else {
cache.put(new Element(userName, 1));
}
}
public boolean isBruteForceAttackLogin(String username) {
Cache cache = manager.getCache(CACHE_NAME);
Element element = cache.get(username);
if (isValid(element)) {
int failureCount = (Integer)element.getObjectValue();
return failureCount >= 3;
} else {
return false;
}
}