使用 API 密鑰和秘密保護 Spring Boot API
一、概述
安全性在 REST API 開發中起著至關重要的作用。不安全的 REST API 可以提供對後端系統上敏感數據的直接訪問。因此,組織需要關注 API 安全。
Spring Security 提供了多種機制來保護我們的 REST API。其中之一是 API 密鑰。 API 密鑰是客戶端在調用 API 時提供的令牌。
在本教程中,我們將討論 Spring Security 中基於 API 密鑰的身份驗證的實現。
2. REST API 安全
Spring Security 可用於保護 REST API。 REST API 是無狀態的。因此,他們不應使用會話或 cookie。相反,這些應該使用基本身份驗證、API 密鑰、JWT 或基於 OAuth2 的令牌來確保安全。
2.1.基本認證
基本身份驗證是一種簡單的身份驗證方案。客戶端發送帶有Authorization
標頭的 HTTP 請求,該標頭包含單詞Basic
後跟一個空格和一個 Base64 編碼的字符串username
: password
。基本身份驗證僅在使用其他安全機制(如 HTTPS/SSL)時才被認為是安全的。
2.2. OAuth2
OAuth2 是 REST API 安全性的事實標準。它是一種開放的身份驗證和授權標準,允許資源所有者通過訪問令牌向客戶授予對私有數據的委託訪問權限。
2.3. API 密鑰
一些 REST API 使用 API 密鑰進行身份驗證。 API 密鑰是一種令牌,用於在不引用實際用戶的情況下向 API 標識 API 客戶端。令牌可以在查詢字符串中或作為請求標頭髮送。與基本身份驗證一樣,可以使用 SSL 隱藏密鑰。
在本教程中,我們重點介紹使用 Spring Security 實現 API 密鑰身份驗證。
3. 使用 API 密鑰保護 REST API
在本節中,我們將創建一個 Spring Boot 應用程序並使用基於 API 密鑰的身份驗證來保護它。
3.1. Maven 依賴項
讓我們首先在pom.xml
中聲明spring-boot-starter-security
依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.2.創建自定義過濾器
這個想法是從請求中獲取 HTTP API 密鑰標頭,然後使用我們的配置檢查密鑰。在這種情況下,我們需要在 Spring Security 配置中添加一個自定義的 Filter 類。
我們將從實施GenericFilterBean
開始。 GenericFilterBean
是一個簡單的javax.servlet.Filter
實現,它是 Spring 感知的。
讓我們創建AuthenticationFilter
類:
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
try {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception exp) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = httpResponse.getWriter();
writer.print(exp.getMessage());
writer.flush();
writer.close();
}
filterChain.doFilter(request, response);
}
}
我們只需要實現一個doFilter()
方法。在此方法中,我們評估 API 密鑰標頭並將生成的Authentication
對象設置到當前的SecurityContext
實例中。
然後,請求被傳遞給其餘的過濾器進行處理,然後路由到DispatcherServlet
,最後路由到我們的控制器。
我們將 API 密鑰的評估和構建Authentication
對象委託給AuthenticationService
類:
public class AuthenticationService {
private static final String AUTH_TOKEN_HEADER_NAME = "X-API-KEY";
private static final String AUTH_TOKEN = "Baeldung";
public static Authentication getAuthentication(HttpServletRequest request) {
String apiKey = request.getHeader(AUTH_TOKEN_HEADER_NAME);
if (apiKey == null || !apiKey.equals(AUTH_TOKEN)) {
throw new BadCredentialsException("Invalid API Key");
}
return new ApiKeyAuthentication(apiKey, AuthorityUtils.NO_AUTHORITIES);
}
}
在這裡,我們檢查請求是否包含帶有秘密的 API 密鑰標頭。如果標頭為null
或不等於 secret,我們將拋出BadCredentialsException
。如果請求有標頭,它會執行身份驗證,將秘密添加到安全上下文,然後將調用傳遞給下一個安全過濾器。我們的getAuthentication
方法非常簡單——我們只需將 API 密鑰標頭和秘密與靜態值進行比較。
要構造Authentication
對象,我們必須使用 Spring Security 通常用於在標準身份驗證上構建對象的相同方法。因此,讓我們擴展AbstractAuthenticationToken
類並手動觸發身份驗證。
3.3.擴展AbstractAuthenticationToken
為了成功地為我們的應用程序實現身份驗證,我們需要將傳入的 API 密鑰轉換為Authentication
對象,例如 AbstractAuthenticationToken **AbstractAuthenticationToken .**
AbstractAuthenticationToken
類實現Authentication
接口,代表經過身份驗證的請求的秘密/主體。
讓我們創建ApiKeyAuthentication
類:
public class ApiKeyAuthentication extends AbstractAuthenticationToken {
private final String apiKey;
public ApiKeyAuthentication(String apiKey, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.apiKey = apiKey;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return apiKey;
}
}
ApiKeyAuthentication
類是一種AbstractAuthenticationToken
對象,帶有從 HTTP 請求中獲取的apiKey
信息。我們在構造中使用setAuthenticated(true)
方法。因此, Authentication
對象包含apiKey
和authenticated
字段:
3.4.安全配置
我們可以通過創建SecurityFilterChain
bean 以編程方式註冊我們的自定義過濾器。在這種情況下,我們需要在HttpSecurity
實例上使用addFilterBefore()
方法在UsernamePasswordAuthenticationFilter
類之前添加AuthenticationFilter
。
讓我們創建SecurityConfig
類:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.authenticated()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
此外,會話策略設置為STATELESS
,因為我們將使用 REST 端點。
3.5. ResourceController
最後,我們將創建帶有/home
映射的ResourceController
:
@RestController
public class ResourceController {
@GetMapping("/home")
public String homeEndpoint() {
return "Baeldung !";
}
}
3.6.禁用默認自動配置
我們需要放棄安全自動配置。為此,我們排除了SecurityAutoConfiguration
和UserDetailsServiceAutoConfiguration
類:
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class})
public class ApiKeySecretAuthApplication {
public static void main(String[] args) {
SpringApplication.run(ApiKeySecretAuthApplication.class, args);
}
}
現在,應用程序已準備好進行測試。
4.測試
我們可以使用 curl 命令來使用受保護的應用程序。
首先,讓我們嘗試在不提供任何安全憑證的情況下請求/home
:
curl --location --request GET 'http://localhost:8080/home'
我們得到了預期的401 Unauthorized
。
現在讓我們請求相同的資源,但也提供 API 密鑰和秘密來訪問它:
curl --location --request GET 'http://localhost:8080/home' \
--header 'X-API-KEY: Baeldung'
結果,服務器的響應是200 OK
。
5.結論
在本教程中,我們討論了 REST API 安全機制。然後,我們在我們的 Spring Boot 應用程序中實現了 Spring Security,以使用 API 密鑰身份驗證機制來保護我們的 REST API。
與往常一樣,可以在 GitHub 上找到代碼示例。