在Serlvet
中,一组Security Filter
组成SecurityFilterChain
,SecurityFilterChain
的概念就比较好理解,是Spring Security
提供的过滤器链,用于管理本身所有的过滤器,在上面的流程图中已有说明。
SecurityFilterChain
可以被FilterChainProxy
用来确定当前请求应该调用哪些Spring Security Filter
实例。
在整个流程中, FilterchainProxy
决定应该使用哪个SecurityFilterChain
,只有第一个匹配的 SecurityFilterChain
被调用。如果请求的URL
不匹配,则继续尝试每个SecurityFilterChain
。
比如在下图中,如果请求的URL
是 /api/messages/
,那么会匹配到右侧上方的 SecurityFilterChain0 ,如果都不匹配,则会调用支持 /*
的 SecurityFilterChainn 。
Spring Security
中的过滤器是通过 SecurityFilterChain API
插入FilteChainProxy
中的,Filter
实例的顺序非常重要。
Spring Security
中的过滤器按照如下所示 (后续会详细介绍):
可通过以下配置:
@EnableWebSecurity(debug = true)
打印请求URL
匹配SecurityFilterChain
中的Security Filter
信息。
大多数情况下,默认Security Filter足以为应用程序的安全性要求。 但是有时您可能希望将自定义Filter
添加到SecurityFilterChain
中。
一个简单的自定义Filter
代码示例:
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");
boolean hasAccess = isUserAllowed(tenantId);
if (hasAccess) {
filterChain.doFilter(request, response);
return;
}
throw new AccessDeniedException("Access denied");
}
}
上面的示例代码执行以下操作:
AccessDeniedException
将其添加到SecurityFilterChain
代码示例:
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {
@Bean
@Order
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.formLogin(Customizer.withDefaults());
http.passwordManagement(Customizer.withDefaults());
// 将过滤器添加在AuthorizationFilter之前
http.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
return http.build();
}
}
将Filter
使用@Component
注解声明为 Spring Bean
时要特别注意。因为 Spring Boot
会自动将其注册到容器中。 这可能会导致过滤器被调用两次,一次由容器调用,一次由 Spring Security
调用,并且顺序不同。
如果您仍然想将将Filter
声明为Spring bean
以利用依赖注入,并避免重复调用,您可以通过FilterRegistrationBean
并将其Enabled
属性设置为false
来实现。
@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
ExceptionTranslationFilter
允许将 AccessDeniedException
和 AuthenticationException
转换为 HTTP
响应。
ExceptionTranslationFilter
作为Security Filter
之一插入到 FilterChainProxy
中。
下图显示了与其他组件的关系。
①首先,调用应用程序的其余部分,正常继续处理请求。ExceptionTranslationFilterFilterChain.doFilter(request, response)
②如果用户未通过身份验证,或者是开启身份验证,抛出AuthenticationException
异常:
SecurityContextHolder
被清除。HttpServletRequest
AuthenticationEntryPoint
、WWW-Authenticate
③否则拒绝访问, 调用AccessDeniedHandler
处理被拒绝的访问。
如处理安全异常中所示,当请求没有身份验证并且针对需要身份验证的资源时,需要保存身份验证资源的请求缓存,以便在身份验证成功后重新请求。
在 Spring Security
中,这是通过使用RequestCache
实现保存来完成的。
RequestCacheAwareFilter
用于保存RequestCache
。当用户成功进行身份验证时,将用于重播原始请求。
一个简单的仅当参数存在时才检查已保存的请求代码示例:
@Bean
DefaultSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
您可能希望不要在会话中存储用户未经身份验证的请求或您始终希望将用户重定向到主页,而不是他们在登录前尝试访问的页面。为此可以使用NullRequestCache
实现。
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}