# AuthenticationFilter
### 前言
身份驗證器主要用於提升系統安全性,確保每一次 API 存取都已通過授權與驗證。本範例示範如何透過 **IP 白名單** 與 **API Key 驗證** 來進行基本的存取控制。
### 實作
假設今天是要先檢查`IP`或是`API KEY`是否在白名單內,才能進行訪問,實作如下。
加入依賴
```xml=
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
```
appliction.propertiex
```propertiex=
# authentication
demo.api.allowlist=123.45.678.910
demo.api.key[0].value=DEMO_1234-5678-1234-5678
demo.api.key[0].expiry=1798646400000
demo.api.key[1].value=DEMO_9876-5432-9876-5432
demo.api.key[1].expiry=0000000000000
```
DemoAuthenticationConfigurationProperties.java
```java=
@Configuration
@ConfigurationProperties(prefix = "demo.api")
@Data
public class DemoAuthenticationConfigurationProperties {
private List<String> allowlist;
private List<Key> key;
@Data
public static class Key {
private String value;
private Long expiry;
}
}
```
AuthenticationFilter.java
```java=
@Component
@Slf4j
public class AuthenticationFilter extends OncePerRequestFilter {
private final List<DemoAuthenticationConfigurationProperties.Key> gatewebApiKey;
private final List<String> gatewebIpAddressAllowlist;
private static final String API_KEY_HEADER = "DEMO-API-KEY";
public AuthenticationFilter(DemoAuthenticationConfigurationProperties demoAuthenticationConfigurationProperties) {
log.info("DemoAuthenticationConfigurationProperties: {}", demoAuthenticationConfigurationProperties);
this.demoApiKey = demoAuthenticationConfigurationProperties.getKey();
this.demoIpAddressAllowlist = demoAuthenticationConfigurationProperties.getAllowlist();
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
final String requestURI = request.getRequestURI();
final boolean shouldCheckPermission = requestURI.startsWith("/demo");
final boolean isAuthorized = isAuthorized(request.getHeader("Authorization"));
final String clientIp = getClientIp(request);
final boolean isAllowedIp = isAllowedIp(clientIp);
if (shouldCheckPermission && !isAuthorized && !isAllowedIp) {
log.warn("Request [{}] and Ip [{}] with [{}] Authorization is unauthorized", request.getRequestURI(), clientIp, request.getHeader("Authorization"));
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
log.debug("Request [{}] and Ip [{}] with [{}] Authorization is authorized", request.getRequestURI(), clientIp, isAuthorized ? "correct" : "incorrect");
}
filterChain.doFilter(request, response);
}
public boolean isAuthorized(String authorization) {
if (authorization == null || authorization.isEmpty()) {
return false;
}
return demoApiKey.stream()
.filter(key -> key.getValue().equals(authorization.substring(API_KEY_HEADER.length() + 1).trim()))
.anyMatch(key -> isValidExpiry(key.getExpiryDate()));
}
private boolean isAllowedIp(String clientIp) {
if (demoIpAddressAllowlist == null || demoIpAddressAllowlist.isEmpty()) {
return false;
}
return demoIpAddressAllowlist.contains(clientIp);
}
private boolean isValidExpiry(LocalDate expiry) {
return !LocalDate.now().isAfter(expiry);
}
}
```
:::info
注入設定檔的值後,就是一些if else檢查判斷,就可以達成過濾啦!
:::
### 總結
本範例透過 Spring Security 的 `OncePerRequestFilter` 實作了一個簡易的身份驗證機制,包含:
1. IP 白名單機制
* 限制只有特定 IP 能存取 API
2. API Key 驗證
* 驗證請求是否攜帶有效 Key
* 並檢查 Key 是否過期
3. 綜合驗證
* 當IP或是API Key任一個是有效的就能存取 API
此設計適合用於:
* 內部 API 保護
* Gateway 前置驗證
* 微服務初步安全控管
### 參考網址
https://chikuwacode.github.io/articles/spring-boot-security-implement-authentication-filter-with-jwt/
https://www.baeldung.com/configuration-properties-in-spring-boot
https://hackmd.io/@Ernie1999/SkyU6JRp-l