Spring Security 是一个安全框架,前身是 Acegi Security,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。

既然Spring Security是基于Filter来实现的,那咱们先从Filter 开始:

1.FilterChain

Filter是在Servlet容器启动前被注册,如下:这是一个典型的过滤链,通过(chain.doFilter(request, response))一层层的过滤请求

spring security cookie实现方案 spring security custom-filter_spring

2.DelegatingFilterProxy

Servlet容器允许使用其自己的标准注册Filters,但它不了解Spring定义的Bean,而DelegatingFilterProxy 在Servlet容器和Spring的ApplicationContext之间搭建了一个桥梁。使得Servlet可以注册在SpringApplicationContext中定义的Filter,如下图所示

spring security cookie实现方案 spring security custom-filter_spring_02

DelegatingFilterProxy从ApplicationContext查找Bean Filter0,然后调用Bean Filter0。 伪代码如下:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

DelegatingFilter还有一个好处:延迟注册Filter Bean

在 spring-web第一期 我曾分析过,因为Servlet容器需要在容器启动之前注册Filter实例。但是,Spring通常使用ContextLoaderListener加载Spring Bean,因此在容器启动前是不可以获取Spring Bean的,直到Spring容器启动后才可以注册这些Filter。

 

3. FilterChainProxy

Spring Security的对Servlet的支持包含在FilterChainProxy中。 FilterChainProxy是Spring Security提供的特殊过滤器,允许通过SecurityFilterChain委派许多过滤器实例。由于FilterChainProxy是一个Bean,因此通常将其包装在DelegatingFilterProxy中。

spring security cookie实现方案 spring security custom-filter_spring_03

4. SecurityFilterChain

FilterChainProxy使用SecurityFilterChain确定应对此请求调用哪些Spring Security过滤器

spring security cookie实现方案 spring security custom-filter_spring boot_04

SecurityFilterChain中的Security FIlter 通常是Bean,但它们使用的是FilterChainProxy而不是DelegatingFilterProxy注册的。 FilterChainProxy具有直接向Servlet容器或DelegatingFilterProxy注册的许多优点。例如,它为Spring Security的所有Servlet支持提供了一个起点。因此,如果您想对Spring Security的Servlet支持进行BUG排除,那么在FilterChainProxy中添加调试点是一个很好的选择。

实际上,FilterChainProxy 甚至可以确定来使用哪一个 SecurityFilterChain, 前提是你可以为你的应用程序提供多个完全独立的配置空间,如下图所示:

spring security cookie实现方案 spring security custom-filter_网站安全_05

FilterChainProxy决定应使用哪个SecurityFilterChain(每个FilterChain中拥有的Filter个数可不相同)。仅匹配的第一个SecurityFilterChain将被调用。如果请求的URL是/ api / messages /,则它将首先与SecurityFilterChain0的/ api / **模式匹配,因此,即使SecurityFilterChain0也与SecurityFilterChainn匹配,也只会调用它。假设都没有匹配上,那么默认使用 SecurityFilterChain n (即最后一个)

5.SecurityFilters的顺序

SecurityFilters 通过SecurityFilterChain API插入到FilterChainProxy中。过滤器的顺序很重要。虽然通常不必要知道Spring Security的过滤器的顺序。但是,有时候了解还是有好处的,如下是Security Filters的完整列表:(蓝色部分是较为重要的过滤器)

  • ChannelProcessingFilter
  • ConcurrentSessionFilter
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • ConcurrentSessionFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

6. Handling Security Exceptions

既然过滤链这么多,那么如果在哪一个环节碰到异常该怎么抛出了,最理想的办法当然是将异常以可阅读,可定位的方式反馈给客户端(如使用HTTP返回)

我们先来说说异常,Spring Security 中最常见的两个异常是:

  • AccessDeniedException (拒绝访问异常)
  • AuthenticationException (认证异常)

spring security cookie实现方案 spring security custom-filter_网站安全_06

 

  •  正常执行过滤链,如果发生异常,则判断是什么类型的异常
  •  如果是AuthenticationException,则对用户进行认证授权,步骤如下
  • 清除 SecurityContextHolder
  • 将请求放入 RequestCache 中进行缓存,如果用户认证通过,则从这里重发此请求
  • AuthenticationEntryPoint用于从客户端请求凭据
  •  如果它是AccessDeniedException,则拒绝访问。并调用AccessDeniedHandler处理拒绝的访问

注:如果应用程序未引发AccessDeniedException或AuthenticationException,则ExceptionTranslationFilter不执行任何操作。

伪代码如下:

try {
    filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException e) {
    if (!authenticated || e instanceof AuthenticationException) {
        startAuthentication(); 
    } else {
        accessDenied(); 
    }
}