Java教程

# Spring Security 01 - 大图景

本文主要是介绍# Spring Security 01 - 大图景,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Spring Security - 大图景

参考:
Spring Security Reference

1. Servlet Filter

filter chain

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
   // do something before the rest of the application
   chain.doFilter(request, response); // invoke the rest of the application
   // do something after the rest of the application
}

因为 Servlet Filter 只能影响下游的 Filter 或 Servlet
所以 Servlet Filter 的顺序很重要

2. DelegatingFilterProxy

Spring 提供了一个 Servlet Filter 实现:DelegatingFilterProxy
它是 Servlet 容器生命周期与 Spring 的 ApplicationContext 之间的桥梁

Servlet 容器允许按照 Servlet 标准注册 Filter
但无法发现 Spring 定义的 Bean
可以使用标准的 Servlet 机制将 DelegatingFilterProxy 注册到 Servlet 容器
然后将所有工作委托给实现了 Filter 接口的 Spring Bean

delegating filter proxy

伪代码

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
   // 懒加载被注册为 Spring Bean 的【Bean Filter0】
   // 下边的 delegate 是指向【Bean Filter0】的引用
   Filter delegate = getFilterBean(someBeanName);
   // 将工作委托给【Bean Filter0】
   delegate.doFilter(request, response);
}

使用 DelegatingFilterProxy 的另一个好处是,可以延迟加载 Filter Bean 实例
这很重要,因为 Servlet 容器需要在启动前注册好 Filter 实例
但 Spring 一般会使用 ContextLoaderListener 来加载 Spring Bean
而 ContextLoaderListener 会在 Filter 实例需要被注册到 Spring 容器时才会开始加载

3. FilterChainProxy

Spring Security 的 Servlet 支持,都是由 FilterChainProxy 来完成的
FilterChainProxy 是 Spring Security 提供的一个特殊的 Filter
它会通过 SecurityFilterChain 把工作委托给多个 Filter 实例
因为 FilterChainProxy 是一个 Spring Bean
因此一般会将其包装在的 DelegatingFilterProxy 中
此时 FilterChainProxy 就是之前提到的【Bean Filter0】

filter chain proxy

package org.springframework.web.filter;

public class DelegatingFilterProxy extends GenericFilterBean {
   ...
   @Nullable
   private WebApplicationContext webApplicationContext;
   
   @Nullable
   private String targetBeanName;
   
   private boolean targetFilterLifecycle = false;

   // 通常为 FilterChainProxy
   @Nullable
   private volatile Filter delegate;
   ...
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException { // 251
  
      // Lazily initialize the delegate if necessary.
      Filter delegateToUse = this.delegate;
      if (delegateToUse == null) {
         synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
               WebApplicationContext wac = findWebApplicationContext();
               if (wac == null) {
                  throw new IllegalStateException("No WebApplicationContext found: " +
                     "no ContextLoaderListener or DispatcherServlet registered?");
               }
               delegateToUse = initDelegate(wac);
            }
            this.delegate = delegateToUse;
         }
      }
  
      // Let the delegate perform the actual doFilter operation.
      invokeDelegate(delegateToUse, request, response, filterChain);
   }
   ...
   protected Filter initDelegate(WebApplicationContext wac) throws ServletException { // 335
      String targetBeanName = getTargetBeanName();
      Assert.state(targetBeanName != null, "No target bean name set");
      /*
      通过名称 targetBeanName 从 Spring 容器中获取 Bean
      targetBeanName 通常为 "springSecurityFilterChain"
      delegate 通常为 FilterChainProxy
      */
      Filter delegate = wac.getBean(targetBeanName, Filter.class);
      if (isTargetFilterLifecycle()) {
         delegate.init(getFilterConfig());
      }
      return delegate;
   }

   protected void invokeDelegate(
      Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException { // 356

      // 将工作委托给 delegate,delegate 通常为 FilterChainProxy
      delegate.doFilter(request, response, filterChain);
   }
   ...
}

4. SecurityFilterChain

一个 FilterChainProxy 对应多个 SecurityFilterChain
FilterChainProxy 根据请求选择一个 SecurityFilterChain
一个 SecurityFilterChain 对应着一个 Filter 序列

security filter chain

SecurityFilterChain 中的 Filter 通常也是 Spring Bean
但没有直接将它们注册到 DelegatingFilterProxy,而是注册到了 FilterChainProxy

是 FilterChainProxy 对象遍历调用的 Filter.doFilter(),不是 SecurityFilterChain 对象
SecurityFilterChain 只是用于分组存放多个 Filter 的容器

相对于将 Filter 直接注册到 Servlet 容器或 DelegatingFilterProxy
将其通过 SecurityFilterChain 注册到 FilterChainProxy 有下面这些好处

  1. 为所有 Spring Security 的 Servlet 支持,提供了一个起始位置
    如果你想对 Spring Security 的 Servlet 支持做故障检测
    可以在 FilterChainProxy 中加 debug 断点
  2. FilterChainProxy 作为使用 Spring Security 的中心
    可以完成一些统一工作,这些工作应该和桥接 Servlet 和 Spring 容器这个关注点相分离
    DelegatingFilterProxy 只做桥接工作
    其它的工作交给 FilterChainProxy,例如:
    • 清理 SecurityContext 避免内存泄漏
    • 使用 Spring Security 的 HttpFirewall 来保护应用,统一防范某些已知类型的攻击
  3. 可以更灵活地选择什么时候使用哪个 Filter
    在 Servlet 容器中,只能基于 URL 来决定是否调用某个 Filter
    (虽然也可以把更灵活的匹配逻辑写到 doFilter() 中,但那样就把匹配的逻辑和 Filter 的功能耦合在了一起)
    而 FilterChainProxy 可以利用 RequestMatcher 接口
    根据 HttpServletRequest 中的任何东西来决定是否调用某个 Filter
    并且还可以决定是否调用某个 SecurityFilterChain
    这样就可以为应用的不同业务分别配置相互隔离的整套安全策略

每个继承了 WebSecurityConfigurerAdapter 的配置类对应一套安全策略
即对应一个 SecurityFilterChain

multi-security filter chain

一个经过 FilterChainProxy 的请求最多只会匹配到其中一个 SecurityFilterChain
如果一个请求可以被多个 SecurityFilterChain 匹配到,那么会调用第一个匹配到的 SecurityFilterChain
各个 SecurityFilterChain 相互独立
甚至可以配置一个没有 Filter 的 SecurityFilterChain
表示某些请求只做 FilterChainProxy 的公共处理,不做额外的安全工作

5. Security Filters

FilterChainProxy 通过 SecurityFilterChain 获取对应的 Filter 序列
这些 Filter 的顺序很重要,通常这些顺序由 Spring 根据其类型固定,客户端程序员不需要知道

下边按顺序列出 Spring Security Filter
ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
OpenIDAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter

6. Handling Security Exceptions

ExceptionTranslationFilter
将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应

exception translation filter

  1. ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 执行之后的逻辑
  2. 如果用户还没有鉴权(即捕获到 AuthenticationException),那么会进入鉴权流程
    1. 清理上下文
      SecurityContextHolder.getContext().setAuthentication(null);
    2. 将 HttpServletRequest 保存到 RequestCache
      当用户成功鉴权后,使用 RequestCache 来重放之前的请求
      this.requestCache.saveRequest(request, response);
    3. 使用 AuthenticationEntryPoint 向客户端请求用户凭证(如密码)
      例如,重定向到登录页,或者发送一个 WWW-Authenticate header
  3. 如果捕捉到的是 AccessDeniedException
    则使用 AccessDeniedHandler 拒绝请求(通常是返回错误码和错误信息)
    this.accessDeniedHandler.handle(request, response, exception);

如果捕获到的既不是 AccessDeniedException 也不是 AuthenticationException
ExceptionTranslationFilter 不会做任何处理,直接再次抛出原来的异常

只有 FilterSecurityInterceptor、SwitchUserFilter 在 ExceptionTranslationFilter 之后
其它 Spring Security 的 Filter 都在前面,而只有后边的 Filter 抛异常才能被捕获
一般的,由 FilterSecurityInterceptor 来统一抛出安全相关的异常

package org.springframework.security.web.access;

public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware {
   ...
   private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException { // 119
      try {
         chain.doFilter(request, response);
      }
      catch (IOException ex) {
         throw ex;
      }
      catch (Exception ex) {
         // Try to extract a SpringSecurityException from the stacktrace
         Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
         RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);
         if (securityException == null) {
            securityException = (AccessDeniedException) this.throwableAnalyzer
               .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
         }
         if (securityException == null) {
            rethrow(ex);
         }
         if (response.isCommitted()) {
            throw new ServletException("Unable to handle the Spring Security Exception "
               + "because the response is already committed.", ex);
         }
         handleSpringSecurityException(request, response, chain, securityException);
      }
	}
   ...
}

其它

一个 SecurityFilterChain 对应一个 AuthenticationManager
一个 AuthenticationManager 对应多个 AuthenticationProvider

Spring Boot 自动配置

  • 创建一个 Servlet Filter 对象作为 Bean 放入 Spring 容器中
    Bean 名称为:springSecurityFilterChain
    这个 Bean 负责为应用提供所有的安全能力
    如:保护应用的 URL、验证提交的用户名密码的有效性、重定向到登录表单等等
  • 创建一个 UserDetailsService Bean
    内置一个用户,用户名为 user,并为其生成一个随机密码,会打印在启动日志中
  • 将容器中名为 springSecurityFilterChain 的 Bean 注册为 Servlet 容器的 Filter
    将这个 Filter 配置为匹配所有的请求 URL
这篇关于# Spring Security 01 - 大图景的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!