在之前的Spring Security:总体架构中,我们讲到Spring Security整个架构是通过Bean容器和Servlet容器对过滤器的支持来实现的。我们将从过滤器出发介绍Spring Security的Servlet类型的认证架构。
AbstractAuthenticationProcessingFilter就是一个SecurityFilterChain中的过滤器,被用作验证用户凭证的基础 Filter。
示意图:

(当用户发送请求时,AbstractAuthenticationProcessingFilter会尝试提取Authentication对象;如果用户未经身份验证或提供的凭证无效,AbstractAuthenticationProcessingFilter 使用 AuthenticationEntryPoint 来生成适当的响应或重定向用户到登录页面。)
接下来介绍过滤器中用到的各种组件。

SecurityContextHolder用于存储全局的用户认证。
一个 SecurityContext 包含一个 Authentication 对象,如果 SecurityContext 包含一个 Authentication,该 SecurityContext 就被用作当前认证的用户。
直接在SecurityContextHolder设置用户,来表明用户已被认证:
- SecurityContext context = SecurityContextHolder.createEmptyContext(); //创建一个空的 SecurityContext
- Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); //创建一个新的 Authentication 对象
- context.setAuthentication(authentication);
- SecurityContextHolder.setContext(context); //在 SecurityContextHolder 上设置 SecurityContext。
提取当前用户信息:
- SecurityContext context = SecurityContextHolder.getContext();
- Authentication authentication = context.getAuthentication();
- String username = authentication.getName();
- Object principal = authentication.getPrincipal();
- Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
SecurityContextHolder使用每个线程的ThreadLocal来存储对应线程的SecurityContext。这意味着 SecurityContext 对同一线程中的方法总是可用的,即使 SecurityContext 没有被明确地作为参数传递给这些方法。
(注意:有些应用程序可能不希望一个线程对应一个安全上下文和一个用户,可以在启动时用策略模式配置 SecurityContextHolder——设置一个系统属性或调用SecurityContextHolder 的一个静态方法,以指定存储上下文的方式,如SecurityContextHolder.MODE_GLOBAL 策略将安全上下文信息将存储在一个静态的全局变量中,使其在整个应用程序中都全局可见。查看有哪些策略可以看SecurityContextHolder 的 JavaDoc。)
Authentication有两个作用:
应用了享元模式的权限实例类,代表了用户的角色(role)和作用域(scope)。
Authentication.getAuthorities()函数会返回GrantedAuthority 实例的集合。
AuthenticationManager 是定义 Spring Security 的 Filter 如何校验认证的接口,最常见的实现是ProviderManager。
ProviderManager会存储一个AuthenticationProvider的List,每个 AuthenticationProvider 执行特定类型的认证(如DaoAuthenticationProvider 支持基于用户名/密码的认证,而 JwtAuthenticationProvider 支持认证JWT令牌)。

每个 AuthenticationProvider 都有机会表明认证应该是成功的、失败的,或者表明它不能做出决定并允许下游的 AuthenticationProvider 来决定。
我们可以自定义一个AuthenticationManager,来决定AuthenticationProvider的顺序、匹配方式等,如:(当然,都是以新版推荐的重写FilterChain的方式进行的)
- @EnableWebSecurity
- @Configuration
- public class MySecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http.authenticationManager(new AuthenticationManager() {
- @Override
- public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- MyUserNamePasswordAuthenticationProvider provider1 = new MyUserNamePasswordAuthenticationProvider();
- if(authentication instanceof UsernamePasswordAuthenticationToken){
- return provider1.authenticate(authentication);
- }
- throw new BadCredentialsException("so?");
- }
- });
- http.authorizeRequests(authorizeRequests ->
- authorizeRequests
- .anyRequest().authenticated()
- )
- .formLogin(); // 使用表单登录
- return http.build();
- }
- }
也可以直接自定义AuthenticationProvider,让默认实现的ProviderManager进行管理:
- @EnableWebSecurity
- @Configuration
- public class MySecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http.authenticationProvider(new MyUserNamePasswordAuthenticationProvider());
- http.authorizeRequests(authorizeRequests ->
- authorizeRequests
- .anyRequest().authenticated()
- )
- .formLogin(); // 使用表单登录
- return http.build();
- }
- }