Spring Boot 應用程序中的共享秘密身份驗證
一、概述
身份驗證是設計安全微服務的基本方面。我們可以通過多種方式實現身份驗證,例如使用基於用戶的憑據、證書或基於令牌的方式。
在本教程中,我們將學習如何為服務到服務通信設置身份驗證。我們將使用 Spring Security 實施該解決方案。
2. 自定義認證介紹
使用身份提供者或密碼數據庫可能並不總是可行的,因為私有微服務不需要基於用戶的交互。但是,我們仍然應該保護應用程序免受任何無效請求的侵害,而不是僅僅依賴網絡安全。
在這種情況下,我們可以通過使用自定義共享秘密標頭來設計一種簡單的身份驗證技術。應用程序將根據預配置的請求標頭驗證請求。
我們還應該在應用程序中啟用 TLS 以保護網絡上的共享秘密。
我們可能還需要確保一些端點在沒有任何身份驗證的情況下工作,例如健康檢查或錯誤端點。
3. 示例應用
假設我們需要使用一些 REST API 構建微服務。
3.1. Maven 依賴項
首先,我們將首先創建一個 Spring Boot Web 項目並包含一些 Spring 依賴項。
讓我們添加[spring-boot-starter-web](https://central.sonatype.com/search?q=spring-boot-starter-web)
、 spring-boot-starter-security
和spring-boot-starter-test
依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3.2.實施 REST 控制器
我們的應用程序有兩個端點,一個端點可通過共享秘密標頭訪問,另一個端點可供網絡中的所有人訪問。
首先,讓我們使用 / hello
端點實現APIController
類:
@GetMapping(path = "/api/hello")
public String hello(){
return "hello";
}
然後,我們將在HealthCheckController
類中實現health
端點:
@GetMapping(path = "/health")
public String getHealthStatus() {
return "OK";
}
4.使用Spring Security實現自定義認證
Spring Security 提供了幾個內置的過濾器類來實現身份驗證。我們還可以覆蓋內置過濾器類或使用身份驗證提供程序來實現自定義解決方案。
我們將配置應用程序以將AuthenticationFilter
註冊到過濾器鏈中。
4.1.實施身份驗證過濾器
要實現基於標頭的身份驗證,我們可以使用RequestHeaderAuthenticationFilter
類。 RequestHeaderAuthenticationFilter
是一個預認證過濾器,它從請求標頭中獲取主體。與任何預授權場景一樣,我們需要將身份驗證證明轉換為具有角色的用戶。
RequestHeaderAuthenticationFilter使用請求標RequestHeaderAuthenticationFilter
設置Principal
對象。在內部,它將使用請求標頭中的Principal
和Credential
創建一個PreAuthenticedAuthenticationToken
對象,並將令牌傳遞給身份驗證管理器。
讓我們添加RequestHeaderAuthenticationFilter
SecurityConfig
類中的Bean
:
@Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("x-auth-secret-key");
filter.setExceptionIfHeaderMissing(false);
filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/**"));
filter.setAuthenticationManager(authenticationManager());
return filter;
}
在上面的代碼中, x-auth-header-key
標頭被添加為Principal
object .
此外,還包括AuthenticationManager
對像以委託實際身份驗證。
我們應該注意,過濾器是為與 /api/** 路徑匹配的端點啟用的。
4.2.設置身份驗證管理器
現在,我們將創建AuthenticationManager
並傳遞一個自定義的AuthenticationProvider
對象,稍後我們將創建該對象:
@Bean
protected AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(requestHeaderAuthenticationProvider));
}
4.3.配置身份驗證提供程序
要實現自定義身份驗證提供程序,我們將實現AuthenticationProvider
接口。
讓我們覆蓋AuthenticationProvider
接口中定義的authenticate
方法:
public class RequestHeaderAuthenticationProvider implements AuthenticationProvider {
@Value("${api.auth.secret}")
private String apiAuthSecret;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String authSecretKey = String.valueOf(authentication.getPrincipal());
if(StringUtils.isBlank(authSecretKey) || !authSecretKey.equals(apiAuthSecret) {
throw new BadCredentialsException("Bad Request Header Credentials");
}
return new PreAuthenticatedAuthenticationToken(authentication.getPrincipal(), null, new ArrayList<>());
}
}
在上面的代碼中, authSecretkey
值與Principal
匹配。如果標頭無效,該方法將拋出 BadCredentialsException BadCredentialsException.
身份驗證成功後,它將返回經過完全身份驗證的PreAuthenticatedAuthenticationToken
對象.
PreAuthenticatedAuthenticationToken
對象可以被視為基於角色授權的用戶。
此外,我們需要重寫AuthenticationProvider
接口中定義的supports
方法:
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
supports
方法檢查此身份驗證提供程序支持的Authentication
類類型。
4.4.使用 Spring Security 配置過濾器
要在應用程序中啟用 Spring Security,我們將添加@EnableWebSecurity
註釋。此外,我們需要創建一個SecurityFilterChain
對象。
此外,Spring Security默認啟用 CORS 和 CSRF 保護。由於此應用程序只能由內部微服務訪問,因此我們將禁用 CORS 和 CSRF 保護。
讓我們在SecurityFilterChain
中包含上面的RequestHeaderAuthenticationFilter :
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter(requestHeaderAuthenticationFilter(), HeaderWriterFilter.class)
.authorizeHttpRequests()
.antMatchers("/api/**").authenticated();
return http.build();
}
}
我們應該注意到會話管理設置為**STATELESS
** ,因為應用程序是在內部訪問的。
4.5.從身份驗證中排除健康端點
使用the antMatcher's permitAll
方法,我們可以從身份驗證和授權中排除任何公共端點。
讓我們在上面的filterchain
方法中添加/health
端點以從身份驗證中排除:
.antMatchers("/health").permitAll()
.and()
.exceptionHandling().authenticationEntryPoint((request, response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
我們應該注意,**異常處理配置為authenticationEntryPoint
**用於返回401 Unauthorized
狀態的 authenticationEntryPoint。
5. 為 API 實施集成測試
使用TestRestTemplate
,我們將為端點實施集成測試。
首先,讓我們通過將有效的x-auth-secret-key
標頭傳遞給/hello
端點來實施測試:
HttpHeaders headers = new HttpHeaders();
headers.add("x-auth-secret-key", "test-secret");
ResponseEntity<String> response = restTemplate.exchange(new URI("http://localhost:8080/app/api"),
HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("hello", response.getBody());
然後,讓我們通過傳遞無效的標頭來實現測試:
HttpHeaders headers = new HttpHeaders();
headers.add("x-auth-secret-key", "invalid-secret");
ResponseEntity<String> response = restTemplate.exchange(new URI("http://localhost:8080/app/api"),
HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
最後,我們將在不添加任何標頭的情況下測試*/health*端點:
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> response = restTemplate.exchange(new URI(HEALTH_CHECK_ENDPOINT),
HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("OK", response.getBody());
正如預期的那樣,身份驗證適用於所需的端點。 /health
端點無需標頭身份驗證即可訪問。
六,結論
在本文中,我們了解了使用帶有共享秘密身份驗證的自定義標頭如何幫助確保服務到服務通信的安全。
我們還了解瞭如何結合使用RequestHeaderAuthenticationFilter
和自定義身份驗證提供程序來實現基於共享秘密的標頭身份驗證。
與往常一樣,可以在 GitHub 上找到示例代碼。