美文网首页JavavaJJava 杂谈Spring Security
Spring Security 启动过程分析

Spring Security 启动过程分析

作者: iHelin | 来源:发表于2018-12-14 18:01 被阅读2次

    准备环境

    以github登录为例,首先创建一个Spring Boot工程,版本为2.1.0.RELEASE,工程结构如下图:

    image.png

    pom.xml如下:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-oauth2-client</artifactId>
                <version>5.1.2.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    

    启动类SecurityApplication.java

    /**
     * @author iHelin
     */
    @RestController
    @SpringBootApplication
    public class SecurityApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SecurityApplication.class, args);
        }
    
        @GetMapping({"/", "/user"})
        public Object get() {
            OAuth2AuthenticationToken authentication = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            OAuth2User principal = authentication.getPrincipal();
            return principal.getAttributes();
        }
    
    }
    

    SercurityConfig.java

    /**
     * @author iHelin
     * @date 2018-11-30 15:47
     */
    @EnableWebSecurity(debug = true)
    public class SercurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests().anyRequest().authenticated().and()
                    .oauth2Login();
        }
    
    }
    

    配置文件application.properties

    server.port=8080
    logging.level.org.springframework.security=debug
    logging.level.org.springframework.boot.autoconfigure.security=debug
    spring.security.oauth2.client.registration.github.client-id=xxxxxx
    spring.security.oauth2.client.registration.github.client-secret=xxxxxx
    

    @EnableWebSecurity开始说起

    SercurityConfig是一个配置类,它继承了WebSecurityConfigurerAdapter,并标明了@EnableWebSecurity(debug = true)注解,查看这个注解发现,里面又导入(import)了WebSecurityConfiguration.class这个配置类,如下图:

    image.png
    WebSecurityConfiguration是一个自动配置类,它的主要作用创建过滤器链(securityFilterChains)并完成安全配置工作,而这一系列过程主要是通过webSecurity完成的。
    系统启动时Spring上下文会首先调用它setFilterChainProxySecurityConfigurer方法进行webSecurity的初始化,这一步通过反射完成(当然,这不是我们的重点)。然后再调用springSecurityFilterChain进行webSecurity的配置,具体步骤如下:
    首先进入springSecurityFilterChain方法
    image.png
    接着调用org.springframework.security.config.annotation.AbstractSecurityBuilder#build
    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");
        }
    

    cas操作进入if语句,进入关键的org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#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;
            }
        }
    

    里面是一个同步的代码块,不过这也不是重点,核心在init和performBuild方法,注意现在我们的主语还是webSecurity。首先看init方法:

    private void init() throws Exception {
            Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    
            for (SecurityConfigurer<O, B> configurer : configurers) {
                configurer.init((B) this);
            }
    
            for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
                configurer.init((B) this);
            }
        }
    

    这里主要看第一个for循环,里面会进行一些配置的初始化,其中会有一个我们继承的WebSecurityConfigurerAdapter的代理,其实也就是我们自己定义的安全配置类SercurityConfig,调用其init方法:

    public void init(final WebSecurity web) throws Exception {
            final HttpSecurity http = getHttp();
            web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
                public void run() {
                    FilterSecurityInterceptor securityInterceptor = http
                            .getSharedObject(FilterSecurityInterceptor.class);
                    web.securityInterceptor(securityInterceptor);
                }
            });
        }
    

    看下getHttp:

    protected final HttpSecurity getHttp() throws Exception {
            ...
            if (!disableDefaults) {
                // @formatter:off
                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();
                // @formatter:on
                ...
            }
            configure(http);
            return http;
        }
    

    里面首先进行默认的配置,这里添加了一个Filter:WebAsyncManagerIntegrationFilter,继续向下执行,会执行configure方法,它是一个模板方法,也就是这里会执行我们配置类里面覆盖的configure方法,这里就完成了httpSecurity的初始化。
    以上步骤都只是webSecurity的init操作,也就是创建了许多的配置器,接下来进入webSecurity的performBuild方法使配置生效,具体过程是调用httpSecurity的config方法,里面会调用上面创建的众多配置器的configure方法,其目的是向过滤器链添加各种Filter,最后还会调用performBuild方法对过滤器进行排序,创建DefaultSecurityFilterChain过滤器链,这里以ExceptionHandlingConfigurer为例。

    public void configure(H http) throws Exception {
            AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
            ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
                    entryPoint, getRequestCache(http));
            AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
            exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
            exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
            http.addFilter(exceptionTranslationFilter);
        }
    

    关键看最后的addFilter方法

    public HttpSecurity addFilter(Filter filter) {
            Class<? extends Filter> filterClass = filter.getClass();
            if (!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.");
            }
            this.filters.add(filter);
            return this;
        }
    

    最终向httpSecurity对象的filters中添加filter。
    然后再调用httpSecurity的performBuild方法对filters进行排序:

    protected DefaultSecurityFilterChain performBuild() throws Exception {
            Collections.sort(filters, comparator);
            return new DefaultSecurityFilterChain(requestMatcher, filters);
        }
    

    最后返回了一个DefaultSecurityFilterChain对象,至此http的配置宣告完成。再回到webSecurity的performBuild方法,它根据httpSecurity返回的securityFilterChain创建了一个securityFilterChains。

    protected Filter performBuild() throws Exception {
            ...
            for (RequestMatcher ignoredRequest : ignoredRequests) {
                securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
            }
            for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
                securityFilterChains.add(securityFilterChainBuilder.build());
            }
            FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
            ...
            Filter result = filterChainProxy;
            ...
            return result;
        }
    

    可以看到filterChainProxy本质上也是一个filter,它最终返回给容器。这样webSecurity的配置也基本完成。

    相关文章

      网友评论

        本文标题:Spring Security 启动过程分析

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