# 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)