美文网首页Spring Security我爱编程Spring boot 初学
Spring Security 实现原理的理解记录

Spring Security 实现原理的理解记录

作者: 不小下 | 来源:发表于2017-09-11 18:58 被阅读899次

    前言

    最近在弄微信的OAuth认证,我们认证服务及作为客户端又作为认证服务端。因此需要对spring cloud oauth的oauth流程做出相应的变动改造。在研究spring cloud oauth的源码时候,发现对spring security 的认证流程不清不楚,改造起来比较吃力。所以对spring security 的实现原理进行了研究学习。

    本文对spring security 的启动流程以及配置源码做了简单分析,希望对大家理解以及以后的使用spring security 有个很好的帮助。


    一切从filter开始

    spring security 的核心就是filter,通过一层层的filter后,才访问到我们的资源信息。感觉就像打魂斗罗,一个关卡一个关卡的打过去,打完最后一关,OK,结局是啥我不记得了。spring security 的filter做着一层一层的拦截,把相关的权限做一层一层的验证。成功?走下层。失败?认证失败。spring security的所有的权限校验都是这样做的,一切的认证都在filter中,业务代码完全不知情。

    URI与Filter的匹配

    每个请求的uri就是个路由地址。spring security中,配置的其中一个部分就是 uri与filter对应关系。换句话就是,每个uri在程序启动的时候,就已经确定了他们所对应的filter列表。这样,每个请求过来,都会经过他所有对应的filter列表,来做一层层的认证授权。如下图。指定了三个uri(支持EL表达式)。/login 的请求经过自己的filter列表,/api/* 以及 /userinfo 也是如此。通过这样的方式来达到控制具体uri权限的能力。

    严谨点说不止是uri,可以自定义匹配头信息啊或者其他的,http请求中可以用来区分的都可以做为匹配条件。这里为了方便理解。

    filter-uri

    Spring Security filter配置与原理

    我们再根据源码分析下spring security 是如何加载存储filter,以及如何处理uri与filter的映射的。

    1. 指定了@EnableWebSecurity,就会加载 WebSecurityConfiguration 的配置;
    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class,
            SpringWebMvcImportSelector.class })
    @EnableGlobalAuthentication
    @Configuration
    public @interface EnableWebSecurity {
        boolean debug() default false;
    }
    
    • 在WebSecurityConfiguration 中,会实例化 WebSecurity;
    @Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        if (debugEnabled != null) {
            webSecurity.debug(debugEnabled);
        }
    
        Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
    
        Integer previousOrder = null;
        Object previousConfig = null;
        for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException(
                        "@Order on WebSecurityConfigurers must be unique. Order of "
                                + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                + config + " too.");
            }
            previousOrder = order;
            previousConfig = config;
        }
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }
    
    1. WebSecurity中可以通过addSecurityFilterChainBuilder() 方法,把各种filter的SecurityBuilder添加进来,而每个SecurityBuilder的泛型上界必须是SecurityFilterChain;
    public WebSecurity addSecurityFilterChainBuilder(
                SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }
    
    1. WebSecurity的performBuild() 方法实例化 FilterChainProxy,FilterChainProxy存储着SecurityFilterChain列表;
    @Override
    protected Filter performBuild() throws Exception {
        Assert.state(
                !securityFilterChainBuilders.isEmpty(),
                "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
                        + WebSecurity.class.getSimpleName()
                        + ".addSecurityFilterChainBuilder directly");
        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
        List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
                chainSize);
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
        if (httpFirewall != null) {
            filterChainProxy.setFirewall(httpFirewall);
        }
        filterChainProxy.afterPropertiesSet();
    
        Filter result = filterChainProxy;
        if (debugEnabled) {
            logger.warn("\n\n"
                    + "********************************************************************\n"
                    + "**********        Security debugging is enabled.       *************\n"
                    + "**********    This may include sensitive information.  *************\n"
                    + "**********      Do not use in a production system!     *************\n"
                    + "********************************************************************\n\n");
            result = new DebugFilter(filterChainProxy);
        }
        postBuildAction.run();
        return result;
    }
    
    1. WebSecurity的performBuild()方法中将securityFilterChainBuilders列表里面的每个SecurityBuilder对象构建SecurityFilterChain实例,将SecurityFilterChain列表作为参数构建FilterChainProxy。

    SecurityFilterChain接口定义了两个方法,一个是存储filter,一个是uri规则匹配。

    public interface SecurityFilterChain {
    
        boolean matches(HttpServletRequest request);
    
        List<Filter> getFilters();
    }
    
    1. 请求到达的时候,FilterChainProxy的dofilter()方法,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证授权。FilterChainProxy的dofilter()中调用了doFilterInternal()方法,如下:
    private void doFilterInternal(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
    
        FirewalledRequest fwRequest = firewall
                .getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall
                .getFirewalledResponse((HttpServletResponse) response);
        // 获取请求对应的filter列表
        List<Filter> filters = getFilters(fwRequest);
    
        if (filters == null || filters.size() == 0) {
            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);
    
            return;
        }
    
        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        // 执行每个filter
        vfc.doFilter(fwRequest, fwResponse);
    }
    
    // 通过遍历filterChains,调用SecurityFilterChain的matches方法,判断当前的请求对应哪些filter,返回匹配的filter列表
    private List<Filter> getFilters(HttpServletRequest request) {
        for (SecurityFilterChain chain : filterChains) {
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
    
        return null;
    }
    

    下图是简单的类图,不过结合文字大体上spring security中是可以大体勾勒出uri与filter的对应关系。具体方法在何时调用,后面会讲到。通过这里我们就可以知道了几个关键类拥有哪些能力,最后在启动的时候把他们串起来,整个流程就清晰了。


    HttpSecurity

    httpSecurity终极作用,就是注册实例化filter

    上一部分中,提到WebSecurity中可以通过addSecurityFilterChainBuilder() 方法,把各种filter的SecurityBuilder添加进来,而这里的SecurityBuilder就是HttpSecurity。

    我们在使用spring security 的时候,经常的做法就是写个类,继承WebSecurityConfigurerAdapter,并且加上@Configuration注解。比如:

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/login").permitAll()
                    .anyRequest().authenticated()
                    .and().formLogin()
                    .permitAll();
        }
        
    }
    

    在自定义认证的时候,对HttpSecurity 的操作是非常核心的动作。这里的http所调用的方法,最终的结果就产生了我们先去提到的uri-filter 的关系映射。

    authorizeRequests(),formLogin()方法分别返回ExpressionUrlAuthorizationConfigurer和FormLoginConfigurer,他们都是SecurityConfigurer接口的实现类,分别代表的是不同类型的安全配置器。而这些安全配置器分别对应一个或多个filter。

    • formLogin对应UsernamePasswordAuthenticationFilter
    • authorizeRequests对应FilterSecurityInterceptor

    authorizeRequests(),formLogin()方法源码

    public final class HttpSecurity extends
            AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
            implements SecurityBuilder<DefaultSecurityFilterChain>,
            HttpSecurityBuilder<HttpSecurity>{
           ...
        public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
                throws Exception {
            return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>())
                    .getRegistry();
        }
        
        public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
            return getOrApply(new FormLoginConfigurer<HttpSecurity>());
        }
        ...
    }
    

    都是调用了getOrApply()方法

    private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
                C configurer) throws Exception {
            C existingConfig = (C) getConfigurer(configurer.getClass());
            if (existingConfig != null) {
                return existingConfig;
            }
            return apply(configurer);
        }
    

    再进去 apply(),到了父类AbstractConfiguredSecurityBuilder中

    // 存储了Class与configurer的Map
    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
    
    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
                throws Exception {
            configurer.addObjectPostProcessor(objectPostProcessor);
            configurer.setBuilder((B) this);
            add(configurer);
            return configurer;
        }
    
    private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
            Assert.notNull(configurer, "configurer cannot be null");
    
            Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
                    .getClass();
            synchronized (configurers) {
                if (buildState.isConfigured()) {
                    throw new IllegalStateException("Cannot apply " + configurer
                            + " to already built object");
                }
                List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
                        .get(clazz) : null;
                if (configs == null) {
                    configs = new ArrayList<SecurityConfigurer<O, B>>(1);
                }
                configs.add(configurer);
                this.configurers.put(clazz, configs);
                if (buildState.isInitializing()) {
                    this.configurersAddedInInitializing.add(configurer);
                }
            }
        }
    
    

    所以最后是将configurer放入一个LinkedHashMap的属性configurers中。每个configurer又对应到filter。
    所以我们平常使用 http.xxx 的操作,就是将configurer添加到这个LinkedHashMap中。


    接口SecurityBuilder 与 接口SecurityConfigurer

    以上提到的都是具体的类,而带入的参数又是接口名,然后搞得云里雾里。WebSecurity,HttpSecurity,UsernamePasswordAuthenticationFilter这些关系怎么感觉还是有点模糊不清。

    现在,我们从更高的层面来说,从两个核心接口以及其实现类的类图来理解下。
    这两个接口就是SecurityBuilder 与 SecurityConfigurer

    public interface SecurityBuilder<O> {
    
        O build() throws Exception;
    }
    
    public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
        
        void init(B builder) throws Exception;
    
        void configure(B builder) throws Exception;
    }
    
    SecurityBuilder
    SecurityConfigurer

    这里描述下:
    HttpSecurity是一个securityBuilder,HttpSecuirty内部维护了一个Filter的List集合,我们添加的各种安全配置器对应的Filter最终都会被加入到这个List集合中。
    WebSecurity是一个securityBuilder,内部维护着securityBuilder的列表,存储securityBuilder,这里主要是存储HttpSecurity。
    很多官方类是XXXConfigurer,这些都是SecurityConfigurer。这些SecurityConfigurer的configure()方法,都会把对应filter添加到HttpSecurity。

    层级的抽样,再结合前面两块的内容,应该能对spring security的整体结构,会有个更好的理解。


    WebSecurity

    WebSecurity 是spring security 中非常重要的一个类!三个重要作用:

    1. 创建Spring Security 过滤链 的 FilterChainProxy
    2. 存储着SecurityBuilder的列表,这里主要是存储各个httpSecurity
    3. 对SecurityConfigurer进行构建

    从这三个作用来看,WebSecurity 是spring security启动的核心。构建各种SecurityConfigurer,存在各种SecurityBuilder,创建好SS的过滤链。是不是把SS的核心功能都初始化完成了。。。

    WebSecurityConfiguration这个配置会在使用@EnableWebSecurity后导入,WebSecurityConfiguration会初始化WebSecurity ,并且调用WebSecurity 的apply()方法。

    WebSecurityConfiguration类中,构建springSecurityFilterChain()方法的时候,调用WebSecurity 的build()方法。

    build()方法是在其父类AbstractSecurityBuilder中实现

    public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }
    

    我们再看看核心的doBuild()

    @Override
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;
    
            beforeInit();
            init();
    
            buildState = BuildState.CONFIGURING;
    
            beforeConfigure();
            configure();
    
            buildState = BuildState.BUILDING;
    
            O result = performBuild();
    
            buildState = BuildState.BUILT;
    
            return result;
        }
    }
    

    这个方法非常的重要,SecurityConfigurer的初始化,构建;FilterChainProxy的初始化等等都在这个方法里面完成。这个方法以后有时间我再进一步的分析分析。


    总结

    上述中更多的描述是SS的启动的流程,以及filter的相关配置存储等等。因为我觉得,filter与uri映射关系成立之后,具体的拦截、认证授权等等,是到了每个filter中。我们把这块流程理通了,需要分析具体的认证原理时候,只需要找到对应的SecurityConfigurer做分析,就可以搞定的。

    流程总结

    再用文字总结下:

    1. spring security 启动过程中 WebSecurityConfiguration 实例化 WebSecurity 。

    WebSecurity 中的 securityFilterChainBuilders 列表存储了各个 SecurityBuilder,这里是HttpSecurity 这个SecurityBuilder。

    每个 httpSecurity.and()... 这样的操作就是把对应的 SecurityConfigurer 加入 HttpSecurity 的 configurers 中存储。

    1. 在构建WebSecurity 的时候,会调用WebSecurity 的doBuild(),这个方法是一个核心操作。
    • 2-1. doBuild() 方法中调用init()方法,会将所有的configurers调用init()方法。
    • 2-2. doBuild() 方法中调用 configure() 方法,调用每个 configurer 的 configure() 方法,把自己加入到 对应 httpSecurity 的 filter list中。
    • 2-3. doBuild() 方法中调用performBuild() 方法,构建FilterChainProxy实例,把 uri的匹配与 httpSecurity 做关联,进入了spring security的过滤链
    1. 就形成了 WebSecurity 1:n HttpSecurity HttpSecurity 1:n configurers HttpSecurity 1:n filters

    2. 每个 HttpSecurity 实例都有个 RequestMatcher 用来匹配请求uri,有个 List<Filter> 记录需要经过的 filter,有个 FilterComparator 记录了 filter 的 order

    3. 每个请求到达,FilterChainProxy匹配该请求对应的List<Filter>,进行拦截。

    以上

    如果有存在理解错误,请及时指出。

    我的spring cloud demo的git地址: spring cloud demo

    相关文章

      网友评论

      • 无聊_f6d9:如果sso登录EnableOAuth2Client和EnableResourceServer是一个服务,如何重写过滤器能在访问资源时候未登录跳到授权服务器啊
        不小下:@无聊_f6d9 可以对未认证抛出的异常做处理。exceptionHandling() 处理拦截的异常,再跳到对应的EntryPoint

      本文标题:Spring Security 实现原理的理解记录

      本文链接:https://www.haomeiwen.com/subject/kobtjxtx.html