spring security 的作用

spring security 框架提供了 认证, 授权和常见攻击防御等功能, 其中认证保支持命令式和响应式(reactive)方式。

spring security 架构

spring security 对 servlet的支持是基于 servlet filter的. 下图描述了单个HTTP请求的处理程序的典型分层。
FilterChain.png

FilterChain

客户端发送请求给应用时,容器会创建 filterchain 实例 其中包含 filter 实例 和 servlet。其中 servlet 基于请求 URI 处理 httpservletrequest。 在spring mvc 中, servlet是dispatchServlet实例。 servlet 负责处理单个 httpservletrequest 和 httpservletresponse。上图中的每一个filter都提供如下功能。

  • 防止下游的 filter 或者 servlet 被调用。此时 filter 会 写 httpservletrequest。
  • 修改 httpservletrequest 和 httpservletresponse 用以让下游 filter 或 servlet 处理。

delegatingFilterProxy

spring 提供了 一个名为 delegatingfilterProxy 的 filter 的实现。它允许在 servlet 容器的生命周期和 spring applicationContext 进行桥接。 servlet 容器允许使用自己的标准注册 filter,但无法感知 spring 定义的 bean。
Spring 提供代理类 DelegatingFilterProxy 可以使用标准servlet容器注册机制 将 filter 注入到 filterChain中。 请求到来时, DelegatingFilterProxy 将请求 转发到 spring 容器中实现了 filter 接口的 bean 实例。

下图展示了 delegatingfilterproxy 的原理如下:

delegatingFilterProxy.png
delegatingFilterProxy 从 applicationcontext 中查找 bean 容器 filter0, 然后调用 filter0 的 dofilter 方法。

1
2
3
4
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response);
}

delegatingFilterProxy允许延迟查找Filter beab 的实例,因为需要在容器启动之前注册filter实例。而spring 通常是通过 ContextLoaderListener 加载bean容器,直到需要注册filter实例后才会完成。

FilterChainProxy

spring security 对 servlet 的支持包含在FilterChainProxy中。 filterchainproxy由 spring security提供的一种特殊的filter,允许通过securityFilterChain 向多个filter实例委托。因为FilterChainProxy是 bean,通常被包装为 DelegatingFilterProxy

FilterChainProxy.png

SecurityFilterChain

FilterChainProxy 使用 SecurityFilterChain来决定应当调用哪一个spring security 的 filter实例来处理当前请求。

SecurityFilterChain.png

在 SecurityChain 中的 security filters 是典型的bean,但是是通过FilterChainProxy而非DelegatingFilterProxy注册。
FilterChainProxy 为直接向 servlet 容器或者 委托 DelegatingFilterProxy 注册提供了如下便利:

  1. 它为spring security的servlet提供了起点,如果想要解决spring security中servlet的问题,可以在filterChainProxy中添加断点。
  2. FilterChainProxy 是 spring security 使用的核心,他可以执行非可选任务。
    • 比如,FilterChainProxy 清楚 securityContext以避免内存泄漏。
    • 使用security中的 httpfirewall 以保护应用程序免受某些类型的攻击。
  3. FilterChainProxy 在决定哪个 SecurityFilterChain应当被调用时提供了灵活性。在servlet 容器中, filter仅仅根据URL调用过滤器实例。而 filterChainProxy可以通过RequestMatcher接口,基于HttpServletRequest内容来决定调用哪个filter实例。

MultipleSecurityfilterChain.png

如上图所示, FilterChainProxy 决定哪个 SecurityFilterChain 应当被调用。只有第一个命中的 SecurityFilterChain 会被调用。
举个栗子,请求 /api/messages/ 时, 会首先命中 /api/, 故而只有 SecurityFilterChain0会被调用,即使请求也能命中其他 securityFilterChain_n;
如果请求的是 /message/, 无法匹配 /api/
, filterChainProxy 会继续尝试匹配每一个 securityFilterChain。 如果其他 securityFilterChain 都未命中,它将会调用securityFilterChain_n;

注意到上图中 securityFilterChain_0 中包含三个 security filter,而 securityFilterChain_n 中有四个 security filter。
而每个 securityFilterChain 可以是唯一的且是可以单独配置的。事实上, 如果 springsecurity想忽略特定的请求,该 securityFilterChain 没有 security filter。

spring filter

security Filter 通过 SecurityFilterChain的API插入到 FilterChainProxy 中。这些filter可以有不同的目的,比如认证、授权、安全保护。这些过滤器按照特定的顺序执行,以确保在正确的时间调用它们。例如,应当在执行授权的过滤器之前调用执行身份验证的过滤器。通常不需要知道 spring security 过滤器的顺序。通过检查FilterOrderRegistration代码以确定过滤顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}

}

如上代码配置后,filter的加载顺序为

  1. CsrfFilter: 由 HttpSecurity#csrf 添加, 以防止csrf 攻击。
  2. UsernamePasswordAuthenticationFilter: 由 HttpSecurity#formLogin 添加
  3. BasicAuthenticationFilter: 由 HttpSecurity#httpBasic 添加, 2,、3 用以认证
  4. AuthorizationFilter: 由 HttpSecurity#authorizeHttpRequests 添加 用以验证授权

常见的 security filters

异常处理

ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 转化为 HttpResponse
ExceptionTranslationFilter 作为 security filter 被插入到 FilterChainProxy中。

ExceptionTranslationFilter.png

对应的伪代码如下:

1
2
3
4
5
6
7
8
9
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
  1. ExceptionTranslationFilter 调用 doFilter方法 调用应用程序的其余部分
  2. 如果用户为认证或者 有 AuthenticationException,则开始认证
    • 声明 SecurityContextHolder
    • 保存 HttpServletRequest 以保证 在认证成功之后 重放 原始请求
    • AuthenticationEntryPoint 用以从客户端获取请求凭证
  3. 否则,如果是 AccessDeniedException 则拒绝访问。 由 AccessDeniedHandler 处理 禁止访问的请求。
    PS: 如果 应用没有抛出 AuthenticationException 或者 AccessDeniedException, 则 ExceptionTranslationFilter 不做任何动作。

RequestCache

RequestCache: 当请求访问需要认证的资源时,RequestCache 使用 RequestCacheAwareFilter 保存原有请求 HttpServletRequest ,用以在认证通过之后重新请求原始请求。默认情况下使用HttpSessionRequestCache.

1
2
3
4
5
6
7
8
9
10
11
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}

上述代码自定义了RequestCache 的实现,在continue参数存在时检查保存请求的 HttpSession

NullRequestCache

如果不希望存储用户未经认证的请求,则使用如下配置。

1
2
3
4
5
6
7
8
9
10
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}

logging

开启日志追踪以感知具体原因。

1
2
properities
logging.level.org.springframework.security=TRACE

Servlet Authentication Architecture

下面介绍 Servlet 的 认证框架,包含如下组件

  1. SecurityContextHolder : spring security中存储用户已经被认证的细节
  2. SecurityContext :从 SecurityContextHolder 中获取,保存当前认证用户的 Authentication
  3. Authentication : 可以是 AuthenticationManager 的输入,以提供用户提供的用于身份验证的凭据,还是来自SecurityContext的当前用户
  4. GrantedAuthority : 在身份验证上授予主体的权限
  5. AuthenticationManager : 定义 spring security filter 如何处理 authentication 的 API
  6. ProviderManger : AuthenticationManager的一种实现
  7. AuthenticationProvider :被 ProviderManger 使用以处理特定类型的认证请求
  8. AuthenticationEntryPoint : 用于从客户端请求凭据
  9. AbstractAuthenticationProcessingFilter : 用于认证的基础过滤器

SecurityContextHolder

SecurityContextHolder 用于存储被认证用户的细节。Spirng security 不关心 SecurityContextHolder 如何填充。如果包含值,则使用该值作为当前认证的用户。
标识用户已认证的方式是在 SecurityContextHolder 中配置。

  • 创建 SecurityContext 空实例
  • 创建 Authentication 对象,这里使用 Authentication 的简单实现 TestingAuthenticationToken (当然用户名/密码也可以)
  • 为 SecurityContext 设置 Authentication
  • 在 SecurityContextHolder 中设置刚才配置好的 SecurityContext
    1
    2
    3
    4
    5
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
    context.setAuthentication(authentication);
    SecurityContextHolder.setContext(context);

SecurityContextHolder.png

SecurityContext

从 SecurityContextHolder中获取,保存 Authentication 对象。

Authentication

在 spring security 中 Authentication 接口包含两个目的:

  1. 作为 AuthenticationManager 的输入,以提供用户提供的身份验证凭据;
  2. 表示当前经过身份验证的用户。 可以从 SecurityContext中获取 当前的 Authentication
    Authentication 中包含:
  • prinpical: 用以标识用户。 如果使用 用户名/密码认证是,principal将是 UserDetails 实例
  • credentials: 通常是密码, 通常在用户认证后被清除以防止密码泄漏
  • authorities:授予用户的高级权限,是 GrantedAthority 实例。 一般是 roles 或者 scopes。

GrantedAthority

可以通过 Authentication.getAuthoritues()方法获得。返回的 GrantedAthority 对象实例包含 当前用户被赋予的 authority,通常是 ROLE_ADMINISTRATOR。
这些角色通常关联web认证、方法认证、领域对象认证。
通常 GrantedAthority 是面向整个应用权限,而不是针对某个领域对象的。

AuthenticationManager

AuthenticationManager 定义了 spring security filters 该如何处理 认证 Authentication。由调用 AuthenticationManager 的 控制器(spring security filter 实例)在 SecurityContextHolder 上设置返回的身份验证信息。
如果没有集成 spring security, 则可以直接设置 securityContextHolder。
最通用的实现是 ProvidorManager 。

ProvidorManager

ProvidorManager.png

ProvidorManager 是最常用的 AuthenticationManager 实现。 ProvidorManager 维护一系列 AuthenticationProvidor 实例。每个 AuthenticationProvidor 都有机会标识 Authentication 是成功、失败或是标识当前 AuthenticationProvidor 无法抉择,交由下一个 AuthenticationProvidor 决定。 如果配置的 AuthenticationProvidor 中没有一个能够用于认证,将抛出 ProvidorNotFoundException。而当没有配置 ProvidorManger 时,将抛出 AuthenticationException。

原则上,每个 AuthenticationManager 都知道如何处理一种特定的认证。比如 用户名密码、SAML认证等。这允许 AuthenticationManager 执行特定类型的身份认证,同时支持多种身份验证类型,并只公开一个 AuthenticationManager bean。

当然也允许配置 parent AuthenticationManager,当没有 AuthenticationManager 处理当前认证信息时,将使用 parent AuthenticationProvidor。

实际上,多个 ProvidorManager 实例可能共享部分 AuthenticationManager。在这种场景下,多个 SecurityFilterChain 实例 拥有一些相同的 认证方式(parent AuthenticationManager),也拥有不同的认证机制。

MultipleAuthenticationManager.png

在使用用户对象缓存以提升无状态应用程序的性能时,可能会产生问题。如果身份认证中包含对缓存中对象的引用,而该对象已被删除时,就无法再使用缓存值进行身份验证。通常需要在缓存的实现或者返回身份验证对象的AuthenticationProvidor中创建副本。

AuthenticationProvidor

AuthenticationProvidor 用于处理特定类型的认证
比如:使用用户名密码认证,使用的 AuthenticationProvidor 是 DaoAuthenticationProvider 。

AuthenticationEntryPoint

AuthenticationEntryPoint 用于发送 请求客户端credentials 的 http response。
客户端包含 credentials 请求资源时,通常不需要提供从客户端请求 credentials 的 响应。
而当客户端请求一个没有权限访问的资源时, AuthenticationEntryPoint 就派上用场了,用以从客户端请求 credentials。其实现可能是重定向到登录页等。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 通常被用作 验证用户凭证的基础 filter。 在用户凭证能被验证前, spring security通常会通过 AuthenticationEntryPoint 请求登录凭证。然后验证 用户提交的认证请求。
AbstractAuthenticationProcessingFilter.png

  1. AbstractAuthenticationProcessingFilter 使用用户提交的包含登录凭证的httpservletRequest 创建 Authentication 。 Authentication的类型取决于 AbstractAuthenticationProcessingFilter 的子类。比如如果是用户名密码则使用 UsernamePasswordAuthenticationFilter 创建 UsernamePasswordAuthenticationToken 。
  2. Authentication 传入 AuthenticationManager 用作认证
  3. 如果认证失败:
    • 清除 SecurityContextHolder
    • 调用 RememberMeServices.loginFail。如果没有配置 remember ,则无需处理
    • 调用 AuthenticationFailureHandler
  4. 认证成功:
    • SessionAuthenticationStrategy 收到新的登录请求
    • Authentication 被设置到 SecurityContextHolder。如果需要保存 SecurityContext, 则调用 SecurityContextRepository#saveContext存储,以在后续请求中继续使用。
    • 调用 RememberMeServices.loginSuccess
    • ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件
    • 调用AuthenticationSuccessHandler

AbstractAuthenticationProcessingFilter.png

spring security 自动注入(如何实现)

依据 spring自动装配原理,它会自动加载 spring-boot-autoconfigure.jar/META-INF/spring目录下 org.springframework.boot.autoconfigure.AutoConfiguration.imports 中和 spring security 相关的配置,相关配置类如下, 每个配置类都会注入部分bean到 spring 容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration

其中最关键的是 SecurityAutoConfigurationSecurityFilterAutoConfiguration
分别负责注入 springfilterChainProxy (DelegatingFilterProxy) 和 注入 filter

SecurityAutoConfiguration

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@AutoConfiguration(before = UserDetailsServiceAutoConfiguration.class)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}

}
  • SecurityAutoConfiguration 加载须在 UserDetailsServiceAutoConfiguration 之前;
  • 只有在classpath下找到 bean DefaultAuthenticationEventPublisher 才会注入当前 bean。
  • Import标识 SecurityAutoConfiguration 导入了两个配置类: SpringBootWebSecurityConfigurationSecurityDataConfiguration

SpringBootWebSecurityConfiguration 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {

@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}

}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {

}

}

包含两个 静态内部类, 我们关注后一个 WebSecurityEnablerConfiguration
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)表明 如果当前容器中没有bean名为 springSecurityFilterChain ,则自动注入 WebSecurityEnablerConfiguration bean。
此时, @EnableWebSecurity 注解生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
public @interface EnableWebSecurity {

/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;

}

EnableWebSecurity 导入了四个配置类: 主要看下 WebSecurityConfiguration

WebSecurityConfiguration 负责创建 FilterChainProxy (spring security的基础), 然后导入必要的bean。 另外,通过 实现 WebSecurityConfigurer并用 Configuration标注 或者使用websecurityCustomizer标注 可以自定义 websecurity

该类中如下方法负责创建 名为 springSecurityFilterChainFilterChainProxy, 该 bean 的实际类型是 FilterChainProxy ,由 webSecurity.build()创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
if (!hasFilterChain) {
this.webSecurity.addSecurityFilterChainBuilder(() -> {
this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
this.httpSecurity.formLogin(Customizer.withDefaults());
this.httpSecurity.httpBasic(Customizer.withDefaults());
return this.httpSecurity.build();
});
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}

到此, spring security 通过配置类 SecurityAutoConfiguration 生成许多 bean 对象,其中最重要的是 名为springSecurityFilterChainFilterChainProxy对象。

SecurityFilterAutoConfiguration

1
2
3
4
5
6
7
@AutoConfiguration(after = SecurityAutoConfiguration.class) 
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
public class SecurityFilterAutoConfiguration {
// ...
}

必须先加载 SecurityAutoConfiguration
SecurityFilterAutoConfiguration 负责自动装配 spring security filter。SecurityFilterAutoConfiguration 类 和SpringBootWebSecurityConfiguration 单独配置以保障在用户自行配置WebSecurityConfiguration时filter的顺序。

该bean 注入时,依赖 AbstractSecurityWebApplicationInitializer

  • AbstractSecurityWebApplicationInitializer 负责 先于其他任何 filter 注册 delegatingFilterProxy 以使用 springsecurityFilterChain。 如果与 AbstractSecurityWebApplicationInitializer 一起使用,还将注册 ContextLoaderListener.
  • 默认情况下,不支持注册 DelegatingFilterProxy, 但是可以通过重写isAsyncSecuritySupported() getSecurityDispatcherTypes()以支持注册。
  • 其他或早或晚于springSecurityFilterChain的配置可以通过重写 afterSpringSecurityFilterChain(ServletContext) 实现。
  • 另外,AbstractDispatcherServletInitializer的子类将早于 其他 filter注册。 所以一般情况下需要确保先调用 AbstractDispatcherServletInitializer的子类,一般可以通过 Order 或者 Ordered确保 AbstractDispatcherServletInitializer的子类早于 AbstractSecurityWebApplicationInitializer 的子类

此时 SecurityAutoConfiguration 已经向ioc 容器中注入了 名为 springSecurityFilterChain的对象。

当系统执行 ServletWebServerApplicationContext#selfInitialize 时 会调用 已注册的 bean 的 onStartup方法(由ServletContextInitializer接口定义) –> RegistrationBean#onStartup
–> RegistrationBean.register() –> DynamicRegistrationBean#register –> AbstractFilterRegistrationBean#addRegistration

1
2
3
4
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = this.getFilter();
return servletContext.addFilter(this.getOrDeduceName(filter), filter);
}

此处 this.getFilter() –> AbstractFilterRegistrationBean#getFilter –> DelegatingFilterProxyRegistrationBean#getFilter 获得新创建的DelegatingFilterProxy实例。

1
2
3
4
5
6
7
org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean#getFilter
public DelegatingFilterProxy getFilter() {
return new DelegatingFilterProxy(this.targetBeanName, this.getWebApplicationContext()) {
protected void initFilterBean() throws ServletException {
}
};
}

spring security 默认自动注入内容

spring security 实战-请求解析

如何踪每个请求的过滤器调用?

如何添加自定义过滤器 (两种)

实现 Filter 重写 doFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;

public class TenantFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

String tenantId = request.getHeader("X-Tenant-Id"); (1)
boolean hasAccess = isUserAllowed(tenantId); (2)
if (hasAccess) {
filterChain.doFilter(request, response); (3)
return;
}
throw new AccessDeniedException("Access denied"); (4)
}

}
  1. 从header 中获取 tenantId
  2. 检查当前用户是否允许访问 tenantId
  3. 如果有权限,则调用余下的过滤器
  4. 否则,抛出 AccessDeniedException 异常
继承 OncePerRequestFilter

每个请求只会调用一次, 通过 doFilterInternal 处理 HTTPServletRequest 和 HttpServletResponse。

注意事项

在将过滤器声明为Spring bean时要小心,要么用@Component注释它,要么在配置中将其声明为bean,因为Spring Boot会自动将其注册到嵌入的容器中。这可能会导致过滤器被调用两次,一次由容器调用,一次由Spring Security调用,而且调用顺序不同。

如何指定过滤器的顺序?

  1. HttpSecurity#addFilterBefore
  2. HttpSecurity#addFilterAfter
  3. HttpSecurity#addFilterAt

引用

1. Spring Security 请求全过程解析
2. Spring Security的核心功能和加载运行流程的原理分析
3. Hello Spring Security