Spring Security 6.1.x 系列(3)—— 基于过滤器的基础原理(二)

四、SecurityFilterChain

Serlvet中,一组Security Filter组成SecurityFilterChainSecurityFilterChain的概念就比较好理解,是Spring Security 提供的过滤器链,用于管理本身所有的过滤器,在上面的流程图中已有说明。

SecurityFilterChain可以被FilterChainProxy用来确定当前请求应该调用哪些Spring Security Filter实例。

在整个流程中, FilterchainProxy 决定应该使用哪个SecurityFilterChain,只有第一个匹配SecurityFilterChain被调用。如果请求的URL不匹配,则继续尝试每个SecurityFilterChain

比如在下图中,如果请求的URL/api/messages/,那么会匹配到右侧上方的 SecurityFilterChain0 ,如果都不匹配,则会调用支持 /*SecurityFilterChainn

Spring Security 6.1.x 系列(3)—— 基于过滤器的基础原理(二)_第1张图片

五、Security Filters

Spring Security 中的过滤器是通过 SecurityFilterChain API 插入FilteChainProxy中的,Filter实例的顺序非常重要。

Spring Security 中的过滤器按照如下所示 (后续会详细介绍):

  • ForceEagerSessionCreationFilter
  • ChannelProcessingFilter
  • WebAsyncManagerlntegrationFilterSecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApilntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilterFilter
  • SecuritylnterceptorSwitchUserFilter

5.1 打印Security Filter

可通过以下配置:

@EnableWebSecurity(debug = true)

打印请求URL匹配SecurityFilterChain中的Security Filter信息。

5.2 将自定义Filter添加到SecurityFilterChain

大多数情况下,默认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"); 
    }
}

上面的示例代码执行以下操作:

  • 从请求头中获取租户ID。
  • 检查当前用户是否有权访问租户ID。
  • 如果用户具有访问权限,则调用链中的其余筛选器。
  • 如果用户没有访问权限,则抛出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 允许将 AccessDeniedExceptionAuthenticationException 转换为 HTTP 响应。

ExceptionTranslationFilter作为Security Filter之一插入到 FilterChainProxy 中。

下图显示了与其他组件的关系。

Spring Security 6.1.x 系列(3)—— 基于过滤器的基础原理(二)_第2张图片
①首先,调用应用程序的其余部分,正常继续处理请求。ExceptionTranslationFilterFilterChain.doFilter(request, response)

②如果用户未通过身份验证,或者是开启身份验证,抛出AuthenticationException异常:

  • SecurityContextHolder被清除。
  • 请求缓存,以便在身份验证成功后可用于重播原始请求。HttpServletRequest
  • 用于从客户端请求凭据。 例如,它可能会重定向到登录页面或发送请求头。AuthenticationEntryPointWWW-Authenticate

③否则拒绝访问, 调用AccessDeniedHandler处理被拒绝的访问。

七、身份验证间请求缓存

处理安全异常中所示,当请求没有身份验证并且针对需要身份验证的资源时,需要保存身份验证资源的请求缓存,以便在身份验证成功后重新请求。

Spring Security中,这是通过使用RequestCache实现保存来完成的。

7.1 请求缓存

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();
}

7.2 阻止保存请求

您可能希望不要在会话中存储用户未经身份验证的请求或您始终希望将用户重定向到主页,而不是他们在登录前尝试访问的页面。为此可以使用NullRequestCache 实现。

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

你可能感兴趣的:(Spring,Security,6.x系列进阶实战,Spring,Security)