# AuthenticationFilter ### 前言 &nbsp;&nbsp;身份驗證器主要用於提升系統安全性,確保每一次 API 存取都已通過授權與驗證。本範例示範如何透過 **IP 白名單** 與 **API Key 驗證** 來進行基本的存取控制。 ### 實作 &nbsp;&nbsp;假設今天是要先檢查`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檢查判斷,就可以達成過濾啦! ::: ### 總結 &nbsp;&nbsp;本範例透過 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