在 Spring Security 中,AuthenticationManager 和 Authentication 的工作流程如下:
用户提交认证请求
用户通过登录表单或 API 提交其认证信息(例如用户名和密码)。
创建 Authentication 对象
在后端,系统创建一个包含用户凭证的 Authentication 对象。常见的实现类是 UsernamePasswordAuthenticationToken。
传递 Authentication 对象给 AuthenticationManager
未认证的 Authentication 对象被传递给 AuthenticationManager 的 authenticate 方法。
AuthenticationManager 调用 AuthenticationProvider
AuthenticationManager 将认证请求委托给一个或多个 AuthenticationProvider 进行验证。通常使用的是 ProviderManager,它可以包含多个 AuthenticationProvider。
AuthenticationProvider 进行认证
每个 AuthenticationProvider 尝试验证传入的 Authentication 对象:
• 从 UserDetailsService 加载用户信息:AuthenticationProvider 使用 UserDetailsService 加载用户信息。
• 验证用户凭证:AuthenticationProvider 比较传入的凭证和加载的用户信息,如果匹配,则认证成功。
• 创建经过认证的 Authentication 对象:如果认证成功,AuthenticationProvider 创建一个包含用户详细信息和权限的新的 Authentication 对象。
返回认证结果
• 认证成功:AuthenticationManager 返回一个经过认证的 Authentication 对象。此对象包含用户的详细信息和权限。
• 认证失败:如果认证失败,AuthenticationManager 抛出一个 AuthenticationException 异常。
存储认证信息
认证成功后,Spring Security 将 Authentication 对象存储在 SecurityContextHolder 中,以便在后续请求中使用。
后续请求处理
在后续请求中,Spring Security 会从 SecurityContextHolder 中获取当前用户的 Authentication 对象,以确定用户的身份和权限。
- 提交用户名和密码,发送认证请求
- AuthenticationManager: 创建 Authentication 对象并调用 authenticate 方法
- AuthenticationManager->AuthenticationProvider: 委托认证请求
- AuthenticationProvider->UserDetailsService: 加载用户信息
- UserDetailsService-->AuthenticationProvider: 返回用户详情
- AuthenticationProvider-->AuthenticationManager:返回经过认证的Authentication对象
- AuthenticationManagerr-->返回经过认证的 Authentication 对象
- SecurityContextHolder: 存储认证信息
- 返回认证结果
note over SecurityContextHolder: 后续请求中使用存储的认证信息
示例代码:
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
@Configuration
public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager() {
UserDetailsService userDetailsService = inMemoryUserDetailsManager();
AuthenticationProvider authenticationProvider = new CustomAuthenticationProvider(userDetailsService);
return new ProviderManager(Arrays.asList(authenticationProvider));
}
@Bean
public UserDetailsService inMemoryUserDetailsManager() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("{noop}password").roles("USER").build());
manager.createUser(User.withUsername("admin").password("{noop}admin").roles("ADMIN").build());
return manager;
}
private static class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
public CustomAuthenticationProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null && "{noop}".concat(password).equals(userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
} else {
throw new UsernameNotFoundException("Invalid username or password");
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
}
AuthController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/login")
public String login(@RequestBody AuthRequest authRequest) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword());
Authentication authentication = authenticationManager.authenticate(authenticationToken);
if (authentication.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authentication);
return "Authentication successful for user: " + authentication.getName();
} else {
return "Authentication failed";
}
}
}
class AuthRequest {
private String username;
private String password;
// getters and setters
}
通过这个示例代码,我们展示了 AuthenticationManager 和 Authentication 在 Spring Security 中的工作流程。用户提交认证请求,系统创建 Authentication 对象并通过 AuthenticationManager 进行验证,最终将认证信息存储在 SecurityContextHolder 中。