# Spring Security 的防止暴力破解身份驗證 ###### tags: `Spring` `security` `java` `force` `brutal` ## `/pom.xml` ``` <!-- https://github.com/google/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.1-jre</version> </dependency> ``` >Guava is a set of core Java libraries from Google that includes new collection types (such as multimap and multiset), immutable collections, a graph library, and utilities for concurrency, I/O, hashing, caching, primitives, strings, and more! It is widely used on most Java projects within Google, and widely used by many other companies as well. >https://github.com/google/guava ## 匯入監聽器 ``` @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } /** * 加入監聽器 */ @Bean public RequestContextListener requestContextListener() { return new RequestContextListener(); } } ``` ## 建立監聽器 ### 嘗試登入的服務層 ``` @Service public class LoginAttemptService { private final int MAX_ATTEMPT = 5; //設定最大嘗試次數 private LoadingCache<String, Integer> attemptsCache; //建構方法 public LoginAttemptService() { attemptsCache = CacheBuilder.newBuilder().expireAfterWrite( 60, // 失敗後可重新嘗試的時間 TimeUnit.SECONDS // 時間單位 ).build(new CacheLoader<String, Integer>() { public Integer load(String key) { return 0; } }); } /** * 登入成功,將快取清除 * @param key 用戶 IP */ public void loginSucceeded(String key) { attemptsCache.invalidate(key); //清除快取 } /** * 登入失敗,累積嘗試次數 * @param key 用戶 IP */ public void loginFailed(String key) { int attempts = 0; try { attempts = attemptsCache.get(key);//取得快取 IP 的次數 } catch (ExecutionException e) { attempts = 0;//找不到快取 } attempts++; attemptsCache.put(key, attempts);//加入快取 } /** * 檢查是否超過嘗試次數 * @param key 用戶 IP * @return */ public boolean isBlocked(String key) { try { return attemptsCache.get(key) >= MAX_ATTEMPT; } catch (ExecutionException e) { return false; } } } ``` ### 發生登入失敗的動作時,執行 ``` @Component public class AuthenticationFailureListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> { @Autowired private LoginAttemptService loginAttemptService; public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) { WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails(); // 回傳登入的位址 loginAttemptService.loginFailed(auth.getRemoteAddress()); } } ``` ### 發生登入成功的動作時,執行 ``` @Component public class AuthenticationSuccessEventListener implements ApplicationListener<AuthenticationSuccessEvent> { @Autowired private LoginAttemptService loginAttemptService; public void onApplicationEvent(AuthenticationSuccessEvent e) { WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails(); // 回傳登入的位址 loginAttemptService.loginSucceeded(auth.getRemoteAddress()); } } ``` ## 相關筆記 * [Spring Security](https://hackmd.io/@LeeLo/HJvoNobgL) * [Domain Object Security (ACLs) in Spring](https://hackmd.io/@LeeLo/HJD8s_NUH) * [Spring Boot Redis](https://hackmd.io/@LeeLo/HJBTC6Xb8) * [Internationalization](https://hackmd.io/@LeeLo/By-z7tSZL)