1. 概述
在本文檔中,我們將重點介紹如何使用多種機制在 Spring Security 中進行用户身份驗證。
我們將通過配置多個身份驗證提供者來實現這一點。
2. 身份驗證提供者
`AuthenticationProvider> 是用於從特定存儲庫(如數據庫、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);
}
}當然,這只是為了我們示例的目的而實現的簡單實現。
5. 配置多個身份驗證提供者
現在,我們將向我們的 Spring Security 配置中添加 CustomAuthenticationProvider 及其內存身份驗證提供程序。
5.1. Java 配置
在我們的配置類中,我們現在使用 <em >AuthenticationManagerBuilder</em> 創建並添加身份驗證提供者。
首先,我們創建 <em >CustomAuthenticationProvider</em>,然後使用 <em >inMemoryAuthentication()</em> 創建一個內存身份驗證提供者。
我們還確保對“/api/**” URL 模式的訪問需要進行身份驗證。
@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();
}
}5.2. XML 配置
如果希望使用 XML 配置而不是 Java 配置,則可以這樣做:
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="memuser" password="pass"
authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
<security:authentication-provider
ref="customAuthenticationProvider" />
</security:authentication-manager>
<security:http>
<security:http-basic />
<security:intercept-url pattern="/api/**"
access="isAuthenticated()" />
</security:http>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 中配置多個身份驗證提供者。我們使用自定義身份驗證提供者和內存身份驗證提供者來安全地保護了一個簡單的應用程序。我們還編寫了測試以驗證應用程序的訪問需要憑據,並且這些憑據至少可以被我們的一個身份驗證提供者驗證。