如何限制 Spring Security 中的登录尝试?
Posted
技术标签:
【中文标题】如何限制 Spring Security 中的登录尝试?【英文标题】:How can I limit login attempts in Spring Security? 【发布时间】:2011-07-18 02:35:02 【问题描述】:Spring Security 中是否有一些配置或可用模块来限制登录尝试(理想情况下,我希望在随后的失败尝试之间增加等待时间)?如果不是,应该使用 API 的哪一部分?
【问题讨论】:
【参考方案1】:从 Spring 4.2 开始,annotation based event listeners 可用:
@Component
public class AuthenticationEventListener
@EventListener
public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event)
String username = (String) event.getAuthentication().getPrincipal();
// update the failed login count for the user
// ...
【讨论】:
【参考方案2】:实现一个 AuthenticationFailureHandler 更新数据库中的计数/时间。我不会指望使用会话,因为攻击者无论如何都不会发送 cookie。
【讨论】:
不幸的是,很难以这种方式获取主体(为其用户名或 id),因为 getAuthentication 和 getExtraInformation 都已从 AuthenticationException 中弃用,因此您无法从数据库中获取用户(无需解析HttpServletRequest 的参数)。使用 AuthenticationProvider 似乎对 be 有用(如下,类似于 Ritesh 的建议)。【参考方案3】:我最近使用 JMX 实现了一个类似的功能来监控登录失败。请参阅我对问题Publish JMX notifications in using Spring without NotificationPublisherAware 的回答中的代码。身份验证提供程序的身份验证方法的一个方面更新了 MBean,并与通知侦听器(该问题中未显示的代码)一起工作以阻止用户和 IP、发送警报电子邮件,甚至在失败超过阈值时暂停登录。
编辑 与我对问题 Spring security 3 : Save informations about authentification in database 的回答类似,我认为捕获身份验证失败事件(而不是自定义处理程序)并将信息存储在数据库中也将起作用,并且它也将保持代码解耦。
【讨论】:
【参考方案4】:正如 Rob Winch 在 http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security 中所建议的那样,我只是将 DaoAuthenticationProvider
子类化(这也可以使用 Ritesh 建议的方面来完成)以限制失败登录的数量,但您也可以将前置条件断言为好吧:
public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider
@Autowired
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException
// Could assert pre-conditions here, e.g. rate-limiting
// and throw a custom AuthenticationException if necessary
try
return super.authenticate(authentication);
catch (BadCredentialsException e)
// Will throw a custom exception if too many failed logins have occurred
userService.recordLoginFailure(authentication);
throw e;
在 Spring config XML 中,只需引用这个 bean:
<beans id="authenticationProvider"
class="mypackage.LimitingDaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService"
p:passwordEncoder-ref="passwordEncoder"/>
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
请注意,我认为不应该使用依赖于访问AuthenticationException
的authentication
或extraInformation
属性(例如实现AuthenticationFailureHandler
)的解决方案,因为这些属性现在已被弃用(在Spring至少安全 3.1)。
【讨论】:
【参考方案5】:您还可以使用实现 ApplicationListener 的服务来更新数据库中的记录。
查看 spring 应用程序事件。
【讨论】:
我发现这种方式比创建自定义身份验证提供程序的其他解决方案要干净得多。从 spring 4.2 开始,这可以通过注释来处理,进一步将你的类与 Spring Security 框架解耦。【参考方案6】:这是我的实现,希望有所帮助。
-
创建一个表来存储任何无效的登录尝试。
如果无效尝试 > 允许的最大值,请将 UserDetail.accountNonLocked 设置为 false
Spring Security 将为您处理“锁定过程”。 (参考
AbstractUserDetailsAuthenticationProvider
)
最后,扩展了 DaoAuthenticationProvider,并整合了里面的逻辑。
@Component("authenticationProvider")
public class YourAuthenticationProvider extends DaoAuthenticationProvider
@Autowired
UserAttemptsDao userAttemptsDao;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException
try
Authentication auth = super.authenticate(authentication);
//if corrent password, reset the user_attempts
userAttemptsDao.resetFailAttempts(authentication.getName());
return auth;
catch (BadCredentialsException e)
//invalid login, update user_attempts, set attempts+1
userAttemptsDao.updateFailAttempts(authentication.getName());
throw e;
完整的源代码和实现请参考这个-Spring Security limit login attempts example,
【讨论】:
【参考方案7】:-
创建一个表来存储失败尝试的值,例如:user_attempts
编写自定义事件监听器
@Component("authenticationEventListner")
public class AuthenticationEventListener
implements AuthenticationEventPublisher
@Autowired
UserAttemptsServices userAttemptsService;
@Autowired
UserService userService;
private static final int MAX_ATTEMPTS = 3;
static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
@Override
public void publishAuthenticationSuccess(Authentication authentication)
logger.info("User has been logged in Successfully :" +authentication.getName());
userAttemptsService.resetFailAttempts(authentication.getName());
@Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication)
logger.info("User Login failed :" +authentication.getName());
String username = authentication.getName().toString();
UserAttempts userAttempt = userAttemptsService.getUserAttempts(username);
User userExists = userService.findBySSO(username);
int attempts = 0;
String error = "";
String lastAttempted = "";
if (userAttempt == null)
if(userExists !=null )
userAttemptsService.insertFailAttempts(username);
else
attempts = userAttempt.getAttempts();
lastAttempted = userAttempt.getLastModified();
userAttemptsService.updateFailAttempts(username, attempts);
if (attempts + 1 >= MAX_ATTEMPTS)
error = "User account is locked! <br>Username : "
+ username+ "<br>Last Attempted on : " + lastAttempted;
throw new LockedException(error);
throw new BadCredentialsException("Invalid User Name and Password");
3.安全配置
1) @Autowired
@Qualifier("authenticationEventListner")
AuthenticationEventListener authenticationEventListner;
2) @Bean
public AuthenticationEventPublisher authenticationListener()
return new AuthenticationEventListener();
3) @Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//configuring custom user details service
auth.authenticationProvider(authenticationProvider);
// configuring login success and failure event listener
auth.authenticationEventPublisher(authenticationEventListner);
【讨论】:
无需抛出 BadCredentialsException - 它将由事件发布者(例如 ProviderManager)抛出。 侦听器方法的问题:您并没有真正锁定用户帐户。下次用户输入有效凭据时,系统应显示错误消息“您的帐户已锁定”,但实际上它会允许成功验证。以上是关于如何限制 Spring Security 中的登录尝试?的主要内容,如果未能解决你的问题,请参考以下文章
Grails springsecurity LDAP登录,如何按子组限制用户?
spring security登录人数限制并且同一个账号可以踢掉前一个用户配置文件
Spring Security应用开发(09)密码错误次数限制
如何从 Spring Security 中的 java 代码登录用户?