深入理解Spring Security之过滤器责任链模式分析

参考文件:Spring Security 参考手册|Spring Security中文版
学习一个技术想要深入研究肯定需要先看官网介绍,否则你可能不太好入手分析代码,可以根据官网的思路一点点的拆解。这是Spring-Security的中文介绍,有能力的可以看英文原版,对我这样只会hello world的只能看中文文档了。
git源码下载地址:https://github.com/spring-projects/spring-security
Spring Security是一个权限框架权限框架,常见的还有Shiro。权限框架的核心功能就是 认证授权
Spirng Security的核心思路就是采用了责任链模式通过一系列的Filter来实现权限控制(关于责任链模式前面有过简单的介绍责任链模式)。这篇文章主要分析了Spring-Security中怎么生成和使用过滤器链的。
首先找一下Security是怎么生成过滤器链的,并且是按照什么规则确定链表的顺序的,我们看一下配置文件,在这里我们这样配置过:

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                ....
                //在这里添加了过滤器,这里指定了配置过滤器并且配置的位置
               .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
    }

我们在WebSecurityConfigurerAdapter的配置中使用了HttpSecurity并且往里面添加了过滤器。基本上可以确定从这两个类入手了,看一下这两个类的代码:
HttpSecurity

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
    private final HttpSecurity.RequestMatcherConfigurer requestMatcherConfigurer;
    private List<Filter> filters = new ArrayList();
    private RequestMatcher requestMatcher;
    private FilterComparator comparator;
	//核心执行方法
    public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor, AuthenticationManagerBuilder authenticationBuilder, Map<Class<?>, Object> sharedObjects) {
        super(objectPostProcessor);
        this.requestMatcher = AnyRequestMatcher.INSTANCE;
        //filter比较器,我们知道比较器一般就是用来排序的
        this.comparator = new FilterComparator();
        ...
    }
	...//省略部分是一些其他配置方法,暂时只关注过滤器
	//这三个是添加过滤器可以添加末尾谁前面谁后面
    public HttpSecurity addFilter(Filter filter) {
       ...
    }
    public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
       ...
    }
    public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
      ...
    }
    public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
    ...
    }

	//比较器排序
	protected DefaultSecurityFilterChain performBuild() {
        this.filters.sort(this.comparator);
        return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
    }
	...//省略Matcher路由匹配规则
	...//省略匿名内部类
}

在HttpSecurity使用了比较器来对比对Filter排序的,看一下排序规则源码:看一下比较器的代码FilterComparator默认的构造函数,所以默认情况下顺序是写死的,这是因为一些过滤器有上下数据的关联。就好比我们玩游戏,可能第二关需要第一关通关时获取的道具。

 	FilterComparator() {
        FilterComparator.Step order = new FilterComparator.Step(100, 100);
        this.put(ChannelProcessingFilter.class, order.next());
        this.put(ConcurrentSessionFilter.class, order.next());
        this.put(WebAsyncManagerIntegrationFilter.class, order.next());
        this.put(SecurityContextPersistenceFilter.class, order.next());
        this.put(HeaderWriterFilter.class, order.next());
        this.put(CorsFilter.class, order.next());
        this.put(CsrfFilter.class, order.next());
        this.put(LogoutFilter.class, order.next());
        ...
    }

下面看一下WebSecurityConfigurerAdapter的代码:
添加Filter的地方开始我没有找到,但是通过打断点是执行的addFilter,idea穿透不过去了,然后思考了一下认为应该在我们写的配置类的父类WebSecurityConfigurerAdapter,进去一看果然在这,里面有这么一段代码,获取HttpSecurity的

protected final HttpSecurity getHttp() throws Exception {
        if (this.http != null) {
            return this.http;
        } else {
            DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
            this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
            AuthenticationManager authenticationManager = this.authenticationManager();
            this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
            this.authenticationBuilder.authenticationEventPublisher(eventPublisher);
            Map<Class<?>, Object> sharedObjects = this.createSharedObjects();
            this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
            //看这里,一连串的addFilter就是我们要找的添加过滤器链的地方
            if (!this.disableDefaults) {
                ((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.http.csrf().and()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling().and()).headers().and()).sessionManagement().and()).securityContext().and()).requestCache().and()).anonymous().and()).servletApi().and()).apply(new DefaultLoginPageConfigurer())).and()).logout();
               ...
            }
        }
    }

到目前为止我们似乎没有发现在哪里添加的Filter。这个如果大家了解设计模式的话其实还是很容易找到的,有一个接口为HttpSecurityBuilder,在很多的configurer中调用了它(这是源码项目点出来的,jar包模式可能点不出来)。
在这里插入图片描述
随便点开一个看一下代码中,可以确定每个Filter都对应有一个配置类,配置类把它放到HttpSecurity

	@Override
	public void configure(H http) {
		ApplicationContext context = http.getSharedObject(ApplicationContext.class);
		CorsFilter corsFilter = getCorsFilter(context);
		Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
				+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
		http.addFilter(corsFilter);
	}

关于部分过滤器的功能(来源Spring-Security 参考手册中文版-The Security Filter Chain部分):

  • ChannelProcessingFilter,因为它可能需要重定向到其他协议。

  • SecurityContextPersistenceFilter,所以SecurityContext可在SecurityContextHolder在web请求的开始设立,并且当web请求结束时SecurityContext任何改变可以被复制到HttpSession(准备下一个使用Web请求)

  • ConcurrentSessionFilter,因为它使用了SecurityContextHolder功能和需要更新SessionRegistry以反映反映主要正在处理的请求。

  • 认证处理机制 UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等使得SecurityContextHolder可以被修饰以包含有效的Authentication请求令牌

  • SecurityContextHolderAwareRequestFilter,如果你使用它来安装一个Spring Security意识HttpServletRequestWrapper到你的servlet容器

  • JaasApiIntegrationFilter,如果JaasAuthenticationToken是在SecurityContextHolder这将处理FRememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put thereilterChain作为Subject在JaasAuthenticationToken

  • RememberMeAuthenticationFilter,所以如果早期的认证处理机制没有更新SecurityContextHolder并且请求给出一个Cookie使remember-me服务发生,一个合适的记忆Authentication对象将被放在那里

  • AnonymousAuthenticationFilter,这样如果之前的验证执行机制没有更新SecurityContextHolder,一个匿名Authentication对象将被放在那里

  • ExceptionTranslationFilter,捕获任何Spring安全异常,以便响应可以返回一个HTTP错误或适当的AuthenticationEntryPoint可以启动

  • FilterSecurityInterceptor,保护网络的URI,当访问被拒绝引发异常

首先找Security中的一个过滤器,我使用的是SecurityContextPersistenceFilter,打个断点观察一下Security的核心过滤器链:
在这里插入图片描述
下面看一下过滤器链的执行,在文档中有这么一句话:Spring Security的网络基础设施,只能通过委托给FilterChainProxy的一个实例使用。安全过滤器不应该由自己来使用。可以看出过滤器委托给了FilterChainProxy类来执行,我们看一下这个类,代码原理很简单,就是一个一个过滤器执行一直到最后一个。
FilterChainProxy

    public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    //过滤器执行标记
    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    //过滤器链
    private List<SecurityFilterChain> filterChains;
    //校验一般不用
    private FilterChainProxy.FilterChainValidator filterChainValidator;
    //防火墙
    private HttpFirewall firewall;
	...
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        //一般if条件都是true的
        if (clearContext) {
            try {
            	//设置if条件为true
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                //走过滤器链
                this.doFilterInternal(request, response, chain);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            this.doFilterInternal(request, response, chain);
        }

    }
	//执行过滤器链
    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
        List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
        //存在过滤器链
        if (filters != null && filters.size() != 0) {
            FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
            vfc.doFilter(fwRequest, fwResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
            }
            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
        }
    }
	...
    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        //过滤器链
        private final List<Filter> additionalFilters; 
        private final FirewalledRequest firewalledRequest;
        private final int size;
        //当前位置
        private int currentPosition;
		...
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        	//最后一个没有下一节点
            if (this.currentPosition == this.size) {
                if (FilterChainProxy.logger.isDebugEnabled()) {
                 ...
                }
                this.firewalledRequest.reset();
                this.originalChain.doFilter(request, response);
            //非最后一个
            } else {
                ++this.currentPosition;
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    ...
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }
}

是不是很简单的责任链模式。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值