Spring 安全架构 - Web 安全

Web 层(用于 UI 和 HTTP 后端)中的 Spring Security 基于 Servlet Filters,因此通常首先了解 Filters 的作用会很有帮助。下图显示了单个 HTTP 请求的处理的典型分层。

客户端向应用发送请求,然后容器根据请求 URI 的路径确定对它应用哪些过滤器和哪个 servlet。一个 servlet 最多只能处理一个请求,但是过滤器形成一个链,因此它们是有序的,实际上,如果过滤器要处理请求本身,则可以否决链的其余部分。过滤器还可以修改下游过滤器和 Servlet 中使用的请求和/或响应。过滤器还可以修改下游过滤器和 Servlet 中使用的请求和/或响应。过滤器链的顺序非常重要,Spring Boot 通过两种机制对其进行管理:一种是 Filter 类型的 @Beans 可以具有 @Order 或实现 Ordered,另一种是它们可以本身成为 FilterRegistrationBean 的一部分。顺序是其 API 的一部分。一些现成的过滤器定义了它们自己的常量,以帮助表明它们相对于彼此的顺序(例如,Spring Session 的 SessionRepositoryFilterDEFAULT_ORDERInteger.MIN_VALUE + 50,这告诉我们它喜欢早一点在链中,但它不排除在此之前的其他过滤器)。

Spring Security 作为链中的单个 Filter 来安置,其具体类型为 FilterChainProxy,种种原因很快就会变得显而易见。在 Spring Boot 应用中,安全过滤器是 ApplicationContext 中的 @Bean,默认情况下会安装该过滤器,以便将其应用于每个请求。它安置在由 SecurityProperties.DEFAULT_FILTER_ORDER 定义的位置,该位置又由 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER 锚定(Spring Boot 应用希望过滤器包装请求并修改其行为时期望的最大顺序)。但是,还有更多的功能:从容器的角度来看,Spring Security 是单个过滤器,但是在内部有其他过滤器,每个过滤器都扮演着特殊的角色。这里是一个全貌:

图二、Spring Security 是单个物理过滤器,但是将处理委托给一系列内部过滤器。

实际上,安全过滤器中甚至还有一层中间层:它通常以 DelegatingFilterProxy 的形式安装在容器中,而不必是 Spring @Bean。代理委托给一个始终为 @BeanFilterChainProxy,通常使用固定名称 springSecurityFilterChain。它是 FilterChainProxy,它包含所有内部安全性逻辑,这些安全性逻辑在内部排列为一个或多个过滤器链。所有过滤器都具有相同的 API(它们都实现了 Servlet Spec 中的 Filter 接口),并且它们都有机会否决该链的其余部分。

可以有多个过滤器链,它们全部由 Spring Security 在同一顶级 FilterChainProxy 中管理,而对于容器来说都是未知的。Spring Security 过滤器包含一个过滤器链列表,并向与其匹配的第一个链发送请求。下图显示了基于匹配请求路径(/foo/**/** 之前匹配)发生的调度。这是很常见的,但不是匹配请求的唯一方法。该调度过程的最重要特征,只有一个链处理过请求。


图三、Spring Security FilterChainProxy 将请求分派到匹配的第一个链。

没有自定义安全配置的纯 Spring Boot 应用具有多个(称为 n)过滤器链,其中通常 n = 6.前 (n-1)个链只是用来忽略静态资源模式,例如 /css/**/**,以及错误视图 /error(路径可以由用户通过 security.ignored 进行控制。SecurityProperties 配置 bean)。最后一个链与捕获所有路径 /** 匹配,并且更活跃,包含用于身份验证、授权、异常处理、会话处理、标头写入等的逻辑。默认情况下,该链中共有十一个过滤器,但通常情况下,用户不必担心使用哪个过滤器以及何时使用。

注意:容易不知道 Spring Security 内部的所有过滤器这一事实非常重要,尤其是在 Spring Boot 应用中,默认情况下,所有 Filter 类型的 @Bean 都会自动向容器注册。因此,如果要向安全链中添加自定义过滤器,则无需将其设置为 @Bean 或将其包装在明确禁用容器注册的 FilterRegistrationBean 中。

创建并定制过滤器链

Spring Boot 应用(带有 /** 请求匹配器的应用)中默认后备过滤链具有 SecurityProperties.BASIC_AUTH_ORDER 的预定义顺序。我们可以通过设置 security.basic.enabled=false 完全关闭它,也可以将其用作后备并仅以较低的顺序定义其他规则。为此,只需添加类型为 WebSecurityConfigurerAdapter(或 WebSecurityConfigurer)的 @Bean 并使用 @Order 来装配。例如:

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/foo/**")
     ...;
  }
}

这个 bean 将导致 Spring Security 添加一个新的过滤器链并在回退之前对其进行排序。

与另一套资源相比,许多应用对一套资源的访问规则完全不同。例如,承载 UI 和支持 API 的应用可能支持基于 cookie 的身份验证以及对 UI 部件的登录页面的重定向,以及基于令牌的身份验证以及对 API 部件的未经身份验证的请求的 401 响应。每组资源都有其自己的 WebSecurityConfigurerAdapter 以及唯一的顺序和自己的请求匹配器。如果匹配规则重叠,则最早的有序过滤器链将获胜。

分发与授权请求匹配

安全筛选器链(或等效的 WebSecurityConfigurerAdapter)具有请求匹配器,该请求匹配用于确定是否将其应用于 HTTP 请求。一旦决定应用特定的过滤器链,就不再应用其他过滤器链。但是在过滤器链中,可以通过在 HttpSecurity 配置器中设置其他匹配器来对授权进行更细粒度的控制。例如:

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/foo/**")
      .authorizeRequests()
        .antMatchers("/foo/bar").hasRole("BAR")
        .antMatchers("/foo/spam").hasRole("SPAM")
        .anyRequest().isAuthenticated();
  }
}

配置 Spring Security 时最容易犯的一个错误是忘记将这些匹配器应用于不同的进程,一个是整个过滤器链的请求匹配器,另一个是仅选择要应用的访问规则。

将应用安全规则与 Actuator 规则绑定

如果我们将 Spring Boot Actuator 用于管理端点,则可能希望它们是安全的,默认情况下它们将是安全的。实际上,将执行器添加到安全应用后,我们会获得一条仅适用于执行器端点的附近加过滤器链。它由仅匹配执行器端点的请求匹配器定义,并且其顺序为 ManagementServerProperties.BASIC_AUTH_ORDER,该顺序比默认的 SecurityProperties 后备过滤器少五个,因此在进行后备处理之前请先进行查询。

如果希望将应用安全规则用于执行器端点,则可以添加一个比执行器顺序更早订购的过滤器链,并带有一个包括所有执行器端点的请求匹配器。如果我们喜欢执行器端点的默认安全性设置,那么最简单的方法是在执行器端点之后但在回退之前(例如 ManagementServerProperties.BASIC_AUTH_ORDER + 1)添加自己的过滤器。例如:

@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/foo/**")
     ...;
  }
}

注意:Web 层中的 Spring Security 当前与 Servlet API 绑定在一起,因此,它仅在以嵌入式或其它方式在 Servlet 容器中运行应用时才真正适用。但是,它不依赖于 Spring MVC 或 Spring Web 堆栈的其余部分,因此可以在任何 servlet 应用中使用,例如使用 JAX-RS 的 servlet 应用。

发布了228 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/stevenchen1989/article/details/105191002
今日推荐