1. 概述
在本文中,我們將重點介紹使用多種機制在 Spring Security 中進行用户身份驗證的方法。
我們將通過配置多個身份驗證提供者來實現這一點。
2. 身份驗證提供者
一個 身份驗證提供者 是用於從特定存儲庫(如數據庫、LDAP、自定義第三方源等)獲取用户信息的抽象。它使用獲取的用户信息來驗證提供的憑據。簡單來説,當定義了多個身份驗證提供者時,提供者將按照其聲明的順序進行查詢。
為了進行快速演示,我們將配置兩個身份驗證提供者——一個自定義身份驗證提供者和一個內存身份驗證提供者。
3. Maven 依賴項
首先,我們將必要的 Spring Security 依賴項添加到我們的 Web 應用程序中:
<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>
如果沒有 Spring Boot:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.5</version>
</dependency>
這些依賴項的最新版本可以在 spring-security-web、spring-security-core 和 spring-security-config 找到。
4. 自定義身份驗證提供者
現在我們通過實現 AuthenticationProvider 接口來創建一個自定義身份驗證提供者。我們將實現 authenticate 方法——它嘗試身份驗證。輸入 Authentication 對象包含用户提供的用户名和密碼憑據。
authenticate 方法在身份驗證成功時返回一個完全填充的 Authentication 對象。如果身份驗證失敗,則拋出 AuthenticationException 類型異常:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials()
.toString();
if ("externaluser".equals(username) && "pass".equals(password)) {
return new UsernamePasswordAuthenticationToken
(username, password, Collections.emptyList());
} else {
throw new
BadCredentialsException("External system authentication failed");
}
}
@Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
}
當然,這只是為了我們示例的目的而實現的簡單實現。
現在,我們添加了 和內存身份驗證提供程序到 Spring Security 配置中。
在我們的配置類中,我們現在使用 創建並添加身份驗證提供程序。
首先,,然後是內存身份驗證提供程序,通過使用 。
我們還確保訪問 URL 模式 “/api/**” 需要進行身份驗證:
@Configuration @EnableWebSecurity public class MultipleAuthProvidersSecurityConfig { @Autowired CustomAuthenticationProvider customAuthProvider; @Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.authenticationProvider(customAuthProvider); authenticationManagerBuilder.inMemoryAuthentication() .withUser("memuser") .password(passwordEncoder().encode("pass")) .roles("USER"); return authenticationManagerBuilder.build(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager, HandlerMappingIntrospector introspector) throws Exception { MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector); http.httpBasic(Customizer.withDefaults()) .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry .requestMatchers(PathRequest.toH2Console()).authenticated() .requestMatchers(mvcMatcherBuilder.pattern("/api/**")).authenticated()) .authenticationManager(authManager); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }6. 應用
接下來,我們創建一個使用我們兩個身份驗證提供者進行保護的簡單 REST 端點。
要訪問此端點,必須提供有效的用户名和密碼。 我們的身份驗證提供程序將驗證憑據並確定是否允許訪問:
@RestController public class MultipleAuthController { @GetMapping("/api/ping") public String getPing() { return "OK"; } }7. 測試
最後,讓我們現在測試對我們安全應用程序的訪問。 只有在提供有效憑據時,才允許訪問:
@Autowired private TestRestTemplate restTemplate; @Test public void givenMemUsers_whenGetPingWithValidUser_thenOk() { ResponseEntity<String> result = makeRestCallToGetPing("memuser", "pass"); assertThat(result.getStatusCode().value()).isEqualTo(200); assertThat(result.getBody()).isEqualTo("OK"); } @Test public void givenExternalUsers_whenGetPingWithValidUser_thenOK() { ResponseEntity<String> result = makeRestCallToGetPing("externaluser", "pass"); assertThat(result.getStatusCode().value()).isEqualTo(200); assertThat(result.getBody()).isEqualTo("OK"); } @Test public void givenAuthProviders_whenGetPingWithNoCred_then401() { ResponseEntity<String> result = makeRestCallToGetPing(); assertThat(result.getStatusCodeValue()).isEqualTo(401); } @Test public void givenAuthProviders_whenGetPingWithBadCred_then401() { ResponseEntity<String> result = makeRestCallToGetPing("user", "bad_password"); assertThat(result.getStatusCode().value()).isEqualTo(401); } private ResponseEntity<String> makeRestCallToGetPing(String username, String password) { return restTemplate.withBasicAuth(username, password) .getForEntity("/api/ping", String.class, Collections.emptyMap()); } private ResponseEntity<String> makeRestCallToGetPing() { return restTemplate .getForEntity("/api/ping", String.class, Collections.emptyMap()); }8. 結論
在本快速教程中,我們看到了如何在 Spring Security 中配置多個身份驗證提供者。我們使用自定義身份驗證提供者和內存身份驗證提供者來安全地配置了一個簡單的應用程序。
我們還編寫了測試以驗證應用程序的訪問需要通過至少一個身份驗證提供者驗證的憑據。