为了账号安全,请及时绑定邮箱和手机立即绑定

Spring Security 启动过程分析

标签:
Spring

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

webp

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@SpringBootApplicationpublic 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=8080logging.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这个配置类,如下图:

webp

image.png


WebSecurityConfiguration是一个自动配置类,它的主要作用创建过滤器链(securityFilterChains)并完成安全配置工作,而这一系列过程主要是通过webSecurity完成的。
系统启动时Spring上下文会首先调用它setFilterChainProxySecurityConfigurer方法进行webSecurity的初始化,这一步通过反射完成(当然,这不是我们的重点)。然后再调用springSecurityFilterChain进行webSecurity的配置,具体步骤如下:
首先进入springSecurityFilterChain方法

webp

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;



作者:iHelin
链接:https://www.jianshu.com/p/6c705b38f2d9


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消