Spring Security中是否有一些配置或可用模塊來限制登錄嘗試(理想情況下,我希望在後續失敗嘗試之間等待的時間越來越長)?如果沒有,API的哪一部分應該用於這個?如何限制Spring Security中的登錄嘗試?
回答
實現一個AuthenticationFailureHandler,用於更新數據庫中的計數/時間。我不會指望使用會話,因爲攻擊者無論如何都不會發送cookie。
我最近實現了一個類似的功能來監視使用JMX的登錄失敗。請參閱我對Publish JMX notifications in using Spring without NotificationPublisherAware問題的回答中的代碼。身份驗證提供程序的身份驗證方法的一個方面是更新MBean,並與通知偵聽程序(代碼未在該問題中顯示)一起使用來阻止用戶和IP,發送警報電子郵件,甚至在失敗超過閾值時掛起登錄。
編輯
到我的回答質疑Spring security 3 : Save informations about authentification in database類似,我認爲拍攝認證失敗事件(而不是定製處理器),並存儲在數據庫中的信息也將工作,它會保持分離以及代碼。
如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 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
或可能不應該使用屬性(例如實現AuthenticationFailureHandler
),因爲這些屬性現在已被棄用(至少在Spring Security 3.1中)。
您也可以使用實現ApplicationListener <AuthenticationFailureBadCredentialsEvent>的服務來更新數據庫中的記錄。
查看春季應用程序事件。
我發現這種方式比創建自定義身份驗證提供程序的其他解決方案更清潔。從spring 4.2開始,這可以通過批註來處理,從而進一步將類與Spring Security框架分離。 – Jberg 2015-07-27 15:38:24
這是我的實施,希望有所幫助。
- 創建一個表來存儲任何無效的登錄嘗試。
- 如果無效次數> max允許,將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,
從春天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
// ...
}
}
- 創建一個表來存儲失敗的嘗試前值: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.Security配置
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)拋出。 – isobretatel 2017-05-04 16:57:13
聽衆方法存在問題:您沒有真正鎖定用戶帳戶。下次用戶輸入有效憑證時,系統應顯示錯誤消息「您的帳戶被鎖定」,但實際上它將允許成功驗證。 – isobretatel 2017-05-04 17:08:40
在偵聽器中拋出異常並不是一個好習慣。 – isobretatel 2017-05-05 12:40:23
不幸的是,由於getAuthentication和getExtraInformation都不再使用AuthenticationException,所以無法從數據庫獲取用戶(不解析HttpServletRequest的參數),因此很難獲得Principal(用於其用戶名或ID)。使用AuthenticationProvider似乎適用於(下面,類似於Ritesh的建議)。 – paulcm 2013-03-26 18:32:16