Spring Boot如何防暴力破解攻擊?
Spring Security在身份驗證和授權過程中為我們完成了許多工作。暴力破解是Web應用程式上的常見攻擊,惡意使用者會嘗試將密碼猜測作為暴力破解。Spring安全性是一個靈活的框架,並提供擴充套件點來擴充套件或使用核心功能。Spring Security不提供任何現成的功能來進行暴力保護,但提供了一些我們可以使用的擴充套件點。
在本文中,我們將構建Spring Security暴力保護以處理暴力攻擊。有多種處理這些攻擊的選項。
- 某些失敗的嘗試後鎖定帳戶。
- 裝置Cookie –鎖定未知裝置。
- 使用驗證碼可以防止自動攻擊。
在本文中,我們將研究第一個選項。我們將保留每次失敗和成功登入嘗試的艱苦跋涉,如果連續失敗的登入嘗試增加了一定的閾值,我們將鎖定/禁用該帳戶,並讓使用者進行密碼重置或任何其他步驟來響應該帳戶。
本文是Spring Security初學者課程的一部分,您可以從GitHub儲存庫中下載原始碼。
1. Spring安全認證事件
我們將使用Spring安全事件釋出功能來構建我們的暴力保護服務。對於成功或失敗的每個身份驗證,Spring安全性都會發布AuthenticationSuccessEvent或AuthenticationFailureEvent。我們將使用此功能來構建Spring安全性的暴力保護。這是我們策略的高階工作流程。
- 我們將編寫一個自定義身份驗證失敗事件偵聽器。該偵聽器將與基礎服務一起使用,以保持嘗試失敗次數的迷航,並在超過該次數時將其鎖定。
- 成功的身份驗證偵聽器可以重置任何失敗的計數(我們會將失敗的計數器重置為零)。
讓我們構建AuthenticationFailureEventListner一個偵聽特定事件的通知,並在任何身份驗證失敗的情況下通知我們。
package com.javadevjournal.core.security.event; import com.javadevjournal.core.security.bruteforce.BruteForceProtectionService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class AuthenticationFailureListener implements ApplicationListener < AuthenticationFailureBadCredentialsEvent > { private static Logger LOG = LoggerFactory.getLogger(AuthenticationFailureListener.class); @Resource(name = "bruteForceProtectionService") private BruteForceProtectionService bruteForceProtectionService; @Override public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) { String username = event.getAuthentication().getName(); LOG.info("********* login failed for user {} ", username); bruteForceProtectionService.registerLoginFailure(username); } } |
在此事件偵聽器中,監聽AuthenticationFailureBadCredentialsEvent,BruteForceProtectionService會將使用者ID資訊傳遞給我們,該資訊將檢查並在需要時禁用使用者帳戶。
與失敗事件處理程式類似,Spring安全性還將在成功身份驗證時釋出事件,我們將建立一個自定義成功處理程式。該處理程式會將控制元件移交給BruteForceProtectionService,以重置失敗的計數器。
@Component public class AuthenticationSuccessListener implements ApplicationListener < AuthenticationSuccessEvent > { private static Logger LOG = LoggerFactory.getLogger(AuthenticationSuccessListener.class); @Resource(name = "bruteForceProtectionService") private BruteForceProtectionService bruteForceProtectionService; @Override public void onApplicationEvent(AuthenticationSuccessEvent event) { String username = event.getAuthentication().getName(); LOG.info("********* login successful for user {} ", username); bruteForceProtectionService.resetBruteForceCounter(username); } } |
BruteForceProtectionService是Spring的安全蠻力保護的一部分,會執行以下任務:
- 對於每次失敗的登入嘗試,增加失敗的計數器。
- 檢查失敗計數是否超過允許的最大配置。
- 如果計數器失敗超過最大限制,則禁用該帳戶。
- BruteForceProtectionService還將在成功登入後重置計數器。
@Service("bruteForceProtectionService") public class DefaultBruteForceProtectionService implements BruteForceProtectionService { @Value("${jdj.security.failedlogin.count}") private int maxFailedLogins; @Autowired UserRepository userRepository; @Value("${jdj.brute.force.cache.max}") private int cacheMaxLimit; private final ConcurrentHashMap < String, FailedLogin > cache; public DefaultBruteForceProtectionService() { this.cache = new ConcurrentHashMap < > (cacheMaxLimit); //setting max limit for cache } @Override public void registerLoginFailure(String username) { UserEntity user = getUser(username); if (user != null && !user.isLoginDisabled()) { int failedCounter = user.getFailedLoginAttempts(); if (maxFailedLogins < failedCounter + 1) { user.setLoginDisabled(true); //disabling the account } else { //let's update the counter user.setFailedLoginAttempts(failedCounter + 1); } userRepository.save(user); } } @Override public void resetBruteForceCounter(String username) { UserEntity user = getUser(username); if (user != null) { user.setFailedLoginAttempts(0); user.setLoginDisabled(false); userRepository.save(user); } } @Override public boolean isBruteForceAttack(String username) { UserEntity user = getUser(username); if (user != null) { return user.getFailedLoginAttempts() >= maxFailedLogins ? true : false; } return false; } protected FailedLogin getFailedLogin(final String username) { FailedLogin failedLogin = cache.get(username.toLowerCase()); if (failedLogin == null) { //setup the initial data failedLogin = new FailedLogin(0, LocalDateTime.now()); cache.put(username.toLowerCase(), failedLogin); if (cache.size() > cacheMaxLimit) { // add the logic to remve the key based by timestamp } } return failedLogin; } private UserEntity getUser(final String username) { return userRepository.findByEmail(username); } public int getMaxFailedLogins() { return maxFailedLogins; } public void setMaxFailedLogins(int maxFailedLogins) { this.maxFailedLogins = maxFailedLogins; } public class FailedLogin { private int count; private LocalDateTime date; public FailedLogin() { this.count = 0; this.date = LocalDateTime.now(); } public FailedLogin(int count, LocalDateTime date) { this.count = count; this.date = date; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public LocalDateTime getDate() { return date; } public void setDate(LocalDateTime date) { this.date = date; } } } |
確保我們能夠儲存和更新計數以及禁用帳戶,透過UserEntity和UserDetailsService實現。
@Entity @Table(name = "user") public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; @Column(unique = true) private String email; private String password; private String token; private boolean accountVerified; //new fields private int failedLoginAttempts; private boolean loginDisabled; } @Service("userDetailsService") public class CustomUserDetailService implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { final UserEntity customer = userRepository.findByEmail(email); if (customer == null) { throw new UsernameNotFoundException(email); } boolean enabled = !customer.isAccountVerified(); UserDetails user = User.withUsername(customer.getEmail()) .password(customer.getPassword()) .disabled(customer.isLoginDisabled()) .authorities("USER").build(); return user; } } |
請記住,一旦禁用帳戶,即使使用成功憑據進行的登入嘗試也不會解鎖該帳戶。要求使用者使用重置密碼功能來解鎖帳戶。
在登入頁面上顯示錯誤
上面的配置可以確保在兩次彈簧登入失敗嘗試後帳戶都被鎖定,這可能是由於Spring Security蠻力保護。我們還可能希望在我們的登入頁面上顯示自定義錯誤訊息,讓我們對自定義登入頁面及其控制器進行一些更改將以下條目新增到messages.properties檔案:
user.account.locked = Your account has been locked due to multiple failed login attempts. |
一旦使用者超過失敗的登入嘗試,我們將在登入頁面上顯示以上訊息。下一步是對登入頁面控制器進行一些更改。
@Controller @RequestMapping("/login") public class LoginPageController { public static final String LAST_USERNAME_KEY = "LAST_USERNAME"; @Resource(name = "customerAccountService") private CustomerAccountService customerAccountService; @GetMapping public String login(@RequestParam(value = "error", defaultValue = "false") boolean loginError, @RequestParam(value = "invalid-session", defaultValue = "false") boolean invalidSession, final Model model, HttpSession session) { String userName = getUserName(session); if (loginError) { if (StringUtils.isNotEmpty(userName) && customerAccountService.loginDisabled(userName)) { model.addAttribute("accountLocked", Boolean.TRUE); model.addAttribute("forgotPassword", new ResetPasswordData()); return "account/login"; } } } final String getUserName(HttpSession session) { final String username = (String) session.getAttribute(LAST_USERNAME_KEY); if (StringUtils.isNotEmpty(username)) { session.removeAttribute(LAST_USERNAME_KEY); // we don't need it and removing it. } return username; } } |
在登入控制器中做了一些重要的事情。
- 如果正在不同地處理登入錯誤請求引數。獲取此引數後,我們檢查是否鎖定了使用者帳戶。
- 如果它鎖定了使用者帳戶,我們會向客戶顯示不同的錯誤訊息。
- 如果您檢視getUserName()方法,我們將從會話中獲取使用者名稱。
預設情況下,一旦我們在登入控制器中收到控制元件,使用者名稱將在請求中不可用。要顯示自定義訊息,我們需要使用者名稱。我們正在使用Spring安全性失敗處理程式將使用者名稱儲存在會話中。
我們只將使用者名稱儲存在會話中,並讓預設的身份驗證處理程式AuthenticationFailureHandler在身份驗證失敗時執行其工作:
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoginAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public static final String LAST_USERNAME_KEY = "LAST_USERNAME"; public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { request.getSession().setAttribute(LAST_USERNAME_KEY, request.getParameter("username")); super.onAuthenticationFailure(request, response, exception); } } |
在登入HTML中新增條件,以在前端顯示自定義錯誤訊息。我們在登入頁面新增了以下條件。
<div th:if="${param.error!=null and accountLocked ==true}"> <div class="alert alert-danger"> <span th:text="#{user.account.locked}"/> </div> </div> |
相關文章
- Spring Boot介面如何設計防篡改、防重放攻擊Spring Boot
- 如何使用 fail2ban 防禦 SSH 伺服器的暴力破解攻擊AI伺服器
- 釣魚攻擊防不勝防,該如何預防網路釣魚攻擊?
- XXE攻擊攻擊原理是什麼?如何防禦XXE攻擊?
- 網站被攻擊如何防禦網站
- 常見web攻擊型別有哪些?如何預防及防範web攻擊?Web型別
- CC攻擊的原理是什麼?如何防禦CC攻擊?
- XXE攻擊是什麼?如何有效防禦XXE攻擊?
- 如何在 Apache 中抵禦暴力破解和 DDos 攻擊Apache
- 伺服器如何防禦CC攻擊伺服器
- 如何預防駭客、病毒攻擊網站?網站
- 高防伺服器如何防禦網路攻擊伺服器
- 淺談DDOS攻擊攻擊與防禦
- 3、攻擊防範
- 【網路安全】如何有效地防禦DDOS攻擊和CC攻擊?
- CC攻擊原理是什麼?網站被CC攻擊如何防禦?網站
- 什麼是CC攻擊?網站被CC攻擊該如何防禦?網站
- 攻擊面管理預防網路攻擊原理?
- 使用混沌候攻擊測試Spring Boot應用Spring Boot
- 直播行業如何防禦網路攻擊?行業
- 電商平臺如何防禦網路攻擊?
- DevOps 團隊如何防禦 API 攻擊devAPI
- Jenkins如何使用CrumbIssuer防禦CSRF攻擊Jenkins
- 如何防範DDoS攻擊,使自己的網站減緩DDoS攻擊呢?網站
- SQL隱碼攻擊原理是什麼?如何防範SQL隱碼攻擊?SQL
- WEB攻擊與防禦Web
- CSRF攻擊與防禦
- [專業術語]什麼是ARP攻擊?如何防範ARP攻擊?
- 【網路安全入門知識】如何有效防禦DDoS攻擊和CC攻擊?
- 什麼是資料中毒?如何防範攻擊者的AI和ML攻擊?AI
- 如何做好防護SQL隱碼攻擊漏洞SQL
- 網際網路公司如何防禦DDoS攻擊?
- Java HTTP Host 頭攻擊原理以及如何防禦JavaHTTP
- 無線網路攻擊有哪些?如何防護?
- 常見網路攻擊有哪些?如何防禦?
- APT攻擊的危害是什麼?如何預防?APT
- 如何使用負載均衡裝置防禦攻擊負載
- 《DNS攻擊防範科普系列2》 -DNS伺服器怎麼防DDoS攻擊DNS伺服器