Spring Security体系结构

目录

过滤器回顾

DelegatingFilterProxy

FilterChainProxy

SecurityFilterChain

Security Filters

中途总结

FilterChainProxy的创建过程

过滤器链加载过程

Handling Security Exceptions

Saving Requests Between Authentication

RequestCache

Prevent the Request From Being Saved

RequestCacheAwareFilter


过滤器回顾

Spring Security的Servlet支持基于Servlet过滤器,因此首先了解过滤器的作用是有帮助的。下图显示了单个HTTP请求的处理程序的典型分层。

 客户端向应用程序发送请求,容器根据请求URI的路径创建一个FilterChain,其中包含Filter实例和能够处理HttpServletRequest的Servlet。在Spring MVC应用程序中,Servlet是DispatcherServlet的一个实例。一个Servlet最多只能处理一个HttpServletRequest和HttpServletResponse。但是,可以使用多个Filter:

  • 防止下游过滤器实例或Servlet被调用。在这种情况下,Filter通常会写入HttpServletResponse。

  • 修改下游过滤器实例和Servlet使用的HttpServletRequest或HttpServletResponse。

Filter的功能来自于传递给它的FilterChain。

FilterChain Usage Example
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
}

由于Filter只影响下游的Filter实例和Servlet,因此调用每个Filter的顺序非常重要。

DelegatingFilterProxy

Spring提供了一个名为DelegatingFilterProxy的过滤器实现,它允许在Servlet容器的生命周期和Spring的ApplicationContext之间架桥。Servlet容器允许使用自己的标准注册Filter实例,但它不知道spring定义的bean。您可以通过标准Servlet容器机制注册DelegatingFilterProxy,但将所有工作委托给实现Filter的Spring Bean。

DelegatingFilterProxy的UML类图可以看出来,该类实现了javax.servlet包下的Filter接口,所以为Servlet容器的实例。

 下面的图片展示了DelegatingFilterProxy是如何适应Filter实例和FilterChain的。

 DelegatingFilterProxy从ApplicationContext(WebApplicationContext)中查找Bean Filter0,然后调用Bean Filter0。下面的清单显示了DelegatingFilterProxy的伪代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	Filter delegate = getFilterBean(someBeanName);(1)
	delegate.doFilter(request, response);(2)
}

(1)惰性获取已注册为Spring Bean的Filter。对于DelegatingFilterProxy中的例子,委托是Bean Filter0的一个实例。

(2)将工作委托给Spring Bean。

DelegatingFilterProxy的另一个好处是,它允许延迟查找Filter bean实例。这很重要,因为容器需要在容器启动之前注册Filter实例。但是,Spring通常使用ContextLoaderListener来加载Spring bean,直到需要注册Filter实例之后才会完成。

FilterChainProxy

Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxy是Spring Security提供的一个特殊过滤器,它允许通过SecurityFilterChain向多个过滤器实例委托。因为FilterChainProxy是一个Bean,所以它通常被封装在DelegatingFilterProxy中。

FilterChainProxy应该是在DelegatingFilterProxy的doFilter()方法中调用,由WebSecurity中的performBuild()方法封装,performBuild()方法中封装了SecurityFilterChain接口的实现过滤器,SecurityFilterChain为org.springframework.security.web包下的过滤器,可以理解为spring bean。WebSecurityConfiguration类中注册名为springSecurityFilterChain的spring bean,返回了WebSecurity bean。

SecurityFilterChain为一个接口,该接口的实现类只有一个不可变类DefaultSecurityFilterChain,该类的成员属性private final List<Filter> filters中中封装了真实的servlet过滤器。

下图显示了FilterChainProxy的角色。

SecurityFilterChain

 FilterChainProxy使用SecurityFilterChain来确定应该为当前请求调用哪个Spring SecurityFilter实例。

自定义过滤器并添加到过滤器链中需要自定义配置类继承WebSecurityConfigurerAdapter抽象类,重写configure(HttpSecurity http)方法,通过调用该方法参数HttpSecurity实例的addFilterBefore()方法,将自定义过滤器添加到指定位置。HttpSecurity支持三种filter添加策略:

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
 ......
  
     // 将自定义的过滤器添加在指定过滤器之后
    public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
        this.comparator.registerAfter(filter.getClass(), afterFilter);
        return this.addFilter(filter);
    }
    // 将自定义的过滤器添加在指定过滤器之前
    public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
        this.comparator.registerBefore(filter.getClass(), beforeFilter);
        return this.addFilter(filter);
    }

    // 添加一个过滤器,但必须是Spring Security自身提供的过滤器实例或其子过滤器
    public HttpSecurity addFilter(Filter filter) {
        Class<? extends Filter> filterClass = filter.getClass();
        if (!this.comparator.isRegistered(filterClass)) {
            throw new IllegalArgumentException("The Filter class " + filterClass.getName() + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        } else {
            this.filters.add(filter);
            return this;
        }
    }

    // 添加一个过滤器在指定过滤器位置
    public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
        this.comparator.registerAt(filter.getClass(), atFilter);
        return this.addFilter(filter);
    }
    ......    
}

HttpSecurity实例的performBuild()方法构造真正的过滤器链,先排序再构造过滤器链。HttpSecurity的performBuild()方法在Spring Security应用程序启动过程中执行,用于构建和配置SecurityFilterChain。

@Override
protected DefaultSecurityFilterChain performBuild() {
    filters.sort(comparator);
    return new DefaultSecurityFilterChain(requestMatcher, filters);
}

Spring Security应用程序启动过程中,是由Spring容器管理的。Spring Security核心模块会提供一个名为springSecurityFilterChain的Filter实例(上文提到过WebSecurity)。在Spring Security应用程序启动时,Spring容器会对所有bean进行实例化和初始化工作,会执行通过@EnableWebSecurity注释的spring bean,使用该注释的配置类为继承WebSecurityConfigurerAdapter抽象类,执行该抽象类的init()方法,init()方法调用final HttpSecurity http = getHttp()方法,该方法返回了一个HttpSecurity实例。在调用该实例的其他方法之前,会隐式地调用 performBuild() 方法以构建和启用 SecurityFilterChain。下面是一个示例 WebSecurityConfigurerAdapter 子类中的 configure(HttpSecurity http) 方法的实现,以说明 performBuild() 的执行时机:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
               .antMatchers("/admin/**").hasRole("ADMIN")
               .anyRequest().authenticated()
               .and()
            .formLogin()
               .loginPage("/login")
               .permitAll()
               .and()
            .logout()
               .permitAll();
    }
}

在上述代码中,configure(HttpSecurity http) 方法会在 Spring Security 启动期间调用,并传入一个 HttpSecurity 实例。该方法通过 http 对象调用一系列方法来配置安全策略,如 authorizeRequests()formLogin()logout() 等。在调用其他方法之前,performBuild() 方法会自动调用以构建和启用 SecurityFilterChain

具体实现步骤为:

initWebSecurityConfigurerAdapter.init()→
WebSecurityConfigurerAdapter.getHttp()→
WebSecurityConfigurerAdapter.authenticationManager()→
AbstractSecurityBuilder.build()→
AbstractSecurityBuilder.doBuild()→
AbstractConfiguredSecurityBuilder.doBuild()→
AuthenticationManagerBuilder.performBuild()→
WebSecurity.performBuild()→
securityFilterChainBuilder.build()→
HttpSecurity.performBuild()

自定义一个过滤器debug如下图所示:

 SecurityFilterChain 其实就是我们平时所说的 Spring Security 中的过滤器链,它里边定义了两个方法,一个是 matches 方法用来匹配请求,另外一个 getFilters 方法返回一个 List 集合,集合中放着 Filter 对象,当一个请求到来时,用 matches 方法去比较请求是否和当前链吻合,如果吻合,就返回 getFilters 方法中的过滤器,那么当前请求会逐个经过 List 集合中的过滤器。

SecurityFilterChain 接口只有一个实现类,那就是 DefaultSecurityFilterChain。DefaultSecurityFilterChain 其实就相当于是 Spring Security 中的过滤器链,一个 DefaultSecurityFilterChain 代表一个过滤器链,如果系统中存在多个过滤器链,则会存在多个 DefaultSecurityFilterChain 对象。

下图显示了SecurityFilterChain的角色。

SecurityFilterChain中的安全过滤器通常是bean,但它们是在FilterChainProxy而不是DelegatingFilterProxy中注册的。FilterChainProxy为直接向Servlet容器或DelegatingFilterProxy注册提供了许多优点。首先,它为Spring Security的所有Servlet支持提供了一个起点。因此,如果您试图对Spring Security的Servlet支持进行故障排除,那么在FilterChainProxy中添加一个调试点是一个很好的开始。

其次,由于FilterChainProxy是Spring Security使用的核心,它可以执行非可选的任务。例如,它清除SecurityContext以避免内存泄漏。它还应用Spring Security的HttpFirewall来保护应用程序免受某些类型的攻击。

此外,它在确定何时调用SecurityFilterChain方面提供了更大的灵活性。在Servlet容器中,仅根据URL调用Filter实例。然而,FilterChainProxy可以通过使用RequestMatcher接口,基于HttpServletRequest中的任何内容来确定调用。

下图显示了多个SecurityFilterChain实例:

 

在Multiple SecurityFilterChain图中,FilterChainProxy决定应该使用哪个SecurityFilterChain。只调用第一个匹配的SecurityFilterChain。如果请求/api/messages/的URL,它首先匹配/api/**的SecurityFilterChain0模式,因此只调用SecurityFilterChain0,即使它也匹配SecurityFilterChainn。如果请求/messages/的URL,它与/api/**的SecurityFilterChain0模式不匹配,因此FilterChainProxy继续尝试每个SecurityFilterChain。假设没有其他SecurityFilterChain实例匹配,则调用SecurityFilterChain。

注意,SecurityFilterChain0只配置了三个安全过滤器实例。然而,securityfilterchain配置了四个安全过滤器实例。需要注意的是,每个SecurityFilterChain都可以是唯一的,并且可以单独配置。事实上,如果应用程序希望Spring security忽略某些请求,SecurityFilterChain可能没有安全过滤器实例。

Security Filters

使用SecurityFilterChain API将安全过滤器插入到FilterChainProxy中。Filter实例的顺序很重要。通常不需要知道Spring Security的Filter实例的顺序。然而,有时知道顺序是有益的。

以下是Spring安全过滤器排序的综合列表:

中途总结

FilterChainProxy的创建过程

  1. WebSecurity用来创建FilterChainProxy过滤器

    框架用法是写一个自定义配置类,继承WebSecurityConfigurerAdapter,重写几个configure()方法

    WebSecurityConfigurerAdapter就是Web安全配置器的适配器对象

  2. HttpSecurity用来创建过滤器链的每个元素

过滤器链加载过程

  1. SecurityFilterAutoConfiguration类会加载 DelegatingFilterProxyRegistrationBean注册过滤器,名字为springSecurityFilterChain

  2. DelegatingFilterProxy过滤器根据名称springSecurityFilterChain获得FilterChainProxy过滤器

  3. FilterChainProxy过滤器doFilter方法执行过滤器,getFilters方法获得15个过滤器

  4. getFilters方法返回值不为空,创建虚拟过滤器链VirtualFilterChain执行过滤器

Handling Security Exceptions

ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException转换为HTTP响应。

ExceptionTranslationFilter作为安全过滤器之一插入到FilterChainProxy中。

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

 (1)首先,ExceptionTranslationFilter调用FilterChain。doFilter(request, response)来调用应用程序的其余部分。

(2)如果用户未经过身份验证或者是AuthenticationException,则启动身份验证。

  • 清除SecurityContextHolder

  • 保存HttpServletRequest,以便在身份验证成功后可以使用它重播原始请求

  • AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能重定向到登录页面或发送WWW-Authenticate标头

(3)否则,如果是AccessDeniedException,则AccessDenied。调用AccessDeniedHandler来处理拒绝访问。

注意:如果应用程序没有抛出AccessDeniedException或AuthenticationException,则ExceptionTranslationFilter不做任何事情。

ExceptionTranslationFilter的伪代码看起来像这样:

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

(1)如过滤器回顾中所述,调用FilterChain。doFilter(request, response)相当于调用应用程序的其余部分。这意味着如果应用程序的另一部分(FilterSecurityInterceptor或方法security)抛出AuthenticationException或AccessDeniedException,它将被捕获并在这里处理。 (2)如果用户未经过身份验证或者是AuthenticationException,则启动身份验证。 (3)否则,拒绝访问

Saving Requests Between Authentication

如处理安全异常中所示,当请求没有身份验证并且是针对需要身份验证的资源时,需要保存已验证资源的请求,以便在身份验证成功后重新请求。在Spring Security中,这是通过使用RequestCache实现保存HttpServletRequest来完成的。

RequestCache

HttpServletRequest保存在RequestCache中。当用户成功通过身份验证时,将使用RequestCache重播原始请求。RequestCacheAwareFilter使用RequestCache来保存HttpServletRequest。

默认情况下,使用HttpeSsionRequestCache。下面的代码演示了如何定制RequestCache实现,该实现用于在参数continue存在的情况下检查保存请求的HttpSession。

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}

Prevent the Request From Being Saved

不希望在会话中存储用户未经身份验证的请求的原因有很多。您可能希望将该存储空间卸载到用户的浏览器上,或者将其存储在数据库中。或者您可能希望关闭此功能,因为您总是希望将用户重定向到主页,而不是他们在登录前试图访问的页面。

要做到这一点,您可以使用NullRequestCache实现。

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

RequestCacheAwareFilter

RequestCacheAwareFilter使用RequestCache来保存HttpServletRequest。

猜你喜欢

转载自blog.csdn.net/qq_27890899/article/details/130968985