Spring Security解析五:WebSecurityConfigurerAdapter

Spring Security解析三:SecurityFilterChain创建过程章节说到,WebSecurityConfigurerAdapter是构建SecurityFilterChain的关键,在WebSecurityConfigurerAdapter的init方法中会创建一个SecurityBuilder<SecurityFilterChain>类型的实例对象【HttpSecurity】并保存到WebSecurity的securityFilterChainBuilders属性中,后续通过SecurityBuilder来完成SecurityFilterChain的创建。

WebSecurityConfigurerAdapter的源码如下

/**
* 在WebSecurity中会分别执行其init、configure方法。
* 在init中创建的HttpSecurity 在WebSecurity会调用其build方法
* 来得到SecurityFilterChain(里面有一个判断
* 是否匹配某个请求的matches方法,和一个返回多个Filter集合的getFilters方法,
* 这些Filter就是拦截后需要被逐个执行的,所以这些Filter的创建才是关键)
*/
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
        WebSecurityConfigurer<WebSecurity> {

    private AuthenticationConfiguration authenticationConfiguration;
    private AuthenticationManagerBuilder authenticationBuilder;
    private AuthenticationManagerBuilder localConfigureAuthenticationBldr;

    private boolean disableLocalConfigureAuthenticationBldr;
    private boolean authenticationManagerInitialized;
    private AuthenticationManager authenticationManager;
    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
    private HttpSecurity http;
    private boolean disableDefaults;

    // AuthenticationConfiguration为认证配置,
    //在@EnableWebSecurity注解里面的@EnableGlobalAuthentication中
    @Autowired
    public void setAuthenticationConfiguration(
            AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationConfiguration = authenticationConfiguration;
    }

    @Autowired
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;

        //创建用于构建AuthenticationManager的对象
        //与@AuthenticationConfiguration中十分类似
        ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
        LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);

        authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);

        //创建可用于在这里进行配置的AuthenticationManagerBuilder
        //该DefaultPasswordEncoderAuthenticationManagerBuilder在
        //@AuthenticationConfiguration中也使用到了
        localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
            @Override
            public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
                //设置“是否删除凭据”
                authenticationBuilder.eraseCredentials(eraseCredentials);
                return super.eraseCredentials(eraseCredentials);
            }

        };
    }

    /**
     * Gets the {@link AuthenticationManager} to use. The default strategy is if
     * {@link #configure(AuthenticationManagerBuilder)} method is overridden to use the
     * {@link AuthenticationManagerBuilder} that was passed in. Otherwise, autowire the
     * {@link AuthenticationManager} by type.
     *
     * @return the {@link AuthenticationManager} to use
     * @throws Exception
     */
    protected AuthenticationManager authenticationManager() throws Exception {
        if (!authenticationManagerInitialized) {
            //调用下面的方法进行装配(默认是设置disableLocalConfigureAuthenticationBldr为true)
            configure(localConfigureAuthenticationBldr);
            if (disableLocalConfigureAuthenticationBldr) { 
                //不使用本地的配置(则会使用@AuthenticationConfiguration中的配置)
                authenticationManager = authenticationConfiguration
                        .getAuthenticationManager();
            }
            else {
                //使用本地的装配
                authenticationManager = localConfigureAuthenticationBldr.build();
            }
            authenticationManagerInitialized = true;
        }
        return authenticationManager;
    }

    /*
    * 可以通过重写该方法来配置AuthenticationManagerBuilder 
    * 该方法被重写后,@AuthenticationConfiguration中配置
    * 的AuthenticationManager将变得无用
    *(前提是disableLocalConfigureAuthenticationBldr不能被设置为true)
    */
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }

    //创建的实例被添加到sharedObjects中
    protected UserDetailsService userDetailsService() {
        AuthenticationManagerBuilder globalAuthBuilder = context
                .getBean(AuthenticationManagerBuilder.class);
        return new UserDetailsServiceDelegator(Arrays.asList(
                localConfigureAuthenticationBldr, globalAuthBuilder));
    }

    public void init(final WebSecurity web) throws Exception {
        //创建HttpSecurity的实例(该对象后面会详细探讨)
        final HttpSecurity http = getHttp();
        //调用WebSecurity的方法将http传递给它保存,
        //后面WebSecurity会使用http的build()方法来构建Filter,
        //所有真正构建Filter的是这个HttpSecurity 。
        //同时,会通过Lambda表达式创建一个Runnable类型的实例
        //传递给WebSecurity,待创建好Filter实例后,最后回调该方法
        web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
            //供WebSecurity回调的方法
            FilterSecurityInterceptor securityInterceptor = http
                    .getSharedObject(FilterSecurityInterceptor.class);
            //这里只是简单的给WebSecurity的filterSecurityInterceptor属性赋值
            //在WebSecurity的getPrivilegeEvaluator方法中会用到
            web.securityInterceptor(securityInterceptor);
        });
    }

    //该方法默认什么都没做
    public void configure(WebSecurity web) throws Exception {
    }

    //关键方法
    protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }

        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

        /*
        *创建AuthenticationManager实例,可能来源于本类中的配置,
        *也可能来源于其它地方,例如@AuthenticationConfiguration
        */
        AuthenticationManager authenticationManager = authenticationManager();
        /*
        *设置父级AuthenticationManager。当其他的AuthenticationManager不能
        *进行身份认证时,最后使用该AuthenticationManager进行认证。
        *
        * 可见,默认的AuthenticationManager被设置为了父级AuthenticationManager,
        *也就是说只会在我们AuthenticationManager不能进行验证的情况下才会使用这个默认的
        */
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
        authenticationBuilder.authenticationEventPublisher(eventPublisher);
        Map<Class<?>, Object> sharedObjects = createSharedObjects();

        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        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
            ClassLoader classLoader = this.context.getClassLoader();
            /*
            * 从IOC中得到所有AbstractHttpConfigurer类型的Bean,并作为默认的配置加入
            * 所以默认情况下(disableDefaults为false),我们只需要在Bean上下文中
            * 创建AbstractHttpConfigurer类型的Bean也可以添加进配置中
            */
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }
        //配置默认的行为
        configure(http);
        return http;
    }

    /*
    *添加默认的配置;这里的authorizeRequests()方法的调用会配置
    *ExpressionUrlAuthorizationConfigurer对象,而该对象会在拦截链的  
    *末尾添加FilterSecurityInterceptor拦截器
    */
    protected void configure(HttpSecurity http) throws Exception {
        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

        http
            .authorizeRequests()  // ExpressionUrlAuthorizationConfigurer
                .anyRequest().authenticated()  //匹配任何请求、ExpressionUrlAuthorizationConfigurer
                .and()
            .formLogin().and()  //FormLoginConfigurer
            .httpBasic();  //HttpBasicConfigurer
    }

    private Map<Class<?>, Object> createSharedObjects() {
        Map<Class<?>, Object> sharedObjects = new HashMap<>();
        sharedObjects.putAll(localConfigureAuthenticationBldr.getSharedObjects());
        sharedObjects.put(UserDetailsService.class, userDetailsService());
        sharedObjects.put(ApplicationContext.class, context);
        sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
        sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
        return sharedObjects;
    }

    @Autowired(required = false)
    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
        this.trustResolver = trustResolver;
    }

    @Autowired(required = false)
    public void setContentNegotationStrategy(
            ContentNegotiationStrategy contentNegotiationStrategy) {
        this.contentNegotiationStrategy = contentNegotiationStrategy;
    }

    @Autowired
    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
        this.objectPostProcessor = objectPostProcessor;
    }
}

HttpSecurity的继承关系与WebSecurity几乎一样,只是多实现了一个HttpSecurityBuilder<HttpSecurity>接口,同时声明的泛型不一样。当在WebSecurity中调用HttpSecurity的build()方法构建DefaultSecurityFilterChain时,依然会逐个的执行HttpSecurity的beforeInit()、init()、beforeConfigure()、configure()以及performBuild()方法来完成构建。

public final class HttpSecurity extends
        AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> {
    private final RequestMatcherConfigurer requestMatcherConfigurer;
    //保存添加的Filter,拦截请求后要执行的过滤器集合
    private List<Filter> filters = new ArrayList<>();
    //用于匹配请求是否满足执行这些过滤器的条件
    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
    private FilterComparator comparator = new FilterComparator();

    /*
    * @param authenticationBuilder 该对象里面保存了AuthenticationManager、
    * UserDetailsService、AuthenticationProvider对象等
    *
    */
    public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
            AuthenticationManagerBuilder authenticationBuilder,
            Map<Class<?>, Object> sharedObjects) {
        super(objectPostProcessor);
        Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
        //保存到变量中
        setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
        //遍历传递进来的对象并添加都自身的属性变量中
        for (Map.Entry<Class<?>, Object> entry : sharedObjects
                .entrySet()) {
            setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
        }
        //获取上下文对象
        ApplicationContext context = (ApplicationContext) sharedObjects
                .get(ApplicationContext.class);
        this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
    }

    public <C> void setSharedObject(Class<C> sharedType, C object) {
        super.setSharedObject(sharedType, object);
    }

    @Override
    protected void beforeConfigure() throws Exception {
        setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
    }

    @Override
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }
    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);
    }
    public HttpSecurity authenticationProvider(
            AuthenticationProvider authenticationProvider) {
        getAuthenticationRegistry().authenticationProvider(authenticationProvider);
        return this;
    }
    private AuthenticationManagerBuilder getAuthenticationRegistry() {
        return getSharedObject(AuthenticationManagerBuilder.class);
    }
    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;
    }

    ///////////////////////////////以下为配置调用方法/////////////////////////
    //添加OpenIDLoginConfigurer配置
    public OpenIDLoginConfigurer<HttpSecurity> openidLogin() throws Exception {
        return getOrApply(new OpenIDLoginConfigurer<>());
    }

    public HeadersConfigurer<HttpSecurity> headers() throws Exception {
        return getOrApply(new HeadersConfigurer<>());
    }

    public HttpSecurity headers(Customizer<HeadersConfigurer<HttpSecurity>> headersCustomizer) throws Exception {
        headersCustomizer.customize(getOrApply(new HeadersConfigurer<>()));
        return HttpSecurity.this;
    }

    public CorsConfigurer<HttpSecurity> cors() throws Exception {
        return getOrApply(new CorsConfigurer<>());
    }

    public HttpSecurity cors(Customizer<CorsConfigurer<HttpSecurity>> corsCustomizer) throws Exception {
        corsCustomizer.customize(getOrApply(new CorsConfigurer<>()));
        return HttpSecurity.this;
    }
    public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
        return getOrApply(new SessionManagementConfigurer<>());
    }
    public HttpSecurity sessionManagement(Customizer<SessionManagementConfigurer<HttpSecurity>> sessionManagementCustomizer) throws Exception {
        sessionManagementCustomizer.customize(getOrApply(new SessionManagementConfigurer<>()));
        return HttpSecurity.this;
    }
    public PortMapperConfigurer<HttpSecurity> portMapper() throws Exception {
        return getOrApply(new PortMapperConfigurer<>());
    }
    public HttpSecurity portMapper(Customizer<PortMapperConfigurer<HttpSecurity>> portMapperCustomizer) throws Exception {
        portMapperCustomizer.customize(getOrApply(new PortMapperConfigurer<>()));
        return HttpSecurity.this;
    }
    public X509Configurer<HttpSecurity> x509() throws Exception {
        return getOrApply(new X509Configurer<>());
    }
    public HttpSecurity x509(Customizer<X509Configurer<HttpSecurity>> x509Customizer) throws Exception {
        x509Customizer.customize(getOrApply(new X509Configurer<>()));
        return HttpSecurity.this;
    }
    public RememberMeConfigurer<HttpSecurity> rememberMe() throws Exception {
        return getOrApply(new RememberMeConfigurer<>());
    }
    public HttpSecurity rememberMe(Customizer<RememberMeConfigurer<HttpSecurity>> rememberMeCustomizer) throws Exception {
        rememberMeCustomizer.customize(getOrApply(new RememberMeConfigurer<>()));
        return HttpSecurity.this;
    }
    ... ...
}

总的来说,在 WebSecurityConfigurerAdapter 中创建了HttpSecurity 实例,并将该实例存入WebSecurity的集合中,同时在WebSecurityConfigurerAdapter中为HttpSecurity 配置了一些默认的行为,例如CsrfConfigurer,ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer等。到目前为止,从SpringBoot的启动到Security的Filter的创建和注册过程都已完成,接下来的问题是认证和授权又是在哪里进行的呢? 关于这方面的问题留在后面的章节中来探讨。


再来整体回顾下初始化的过程

  1. 在配置类上添加@EnableWebSecurity来启动装配(SpringBoot中该步骤会自动完成);

  2. EnableWebSecurity中导入了配置类WebSecurityConfiguration并执行:

    • 从IOC中收集到所有SecurityConfigurer<Filter, WebSecurity>类型的Bean并根据Order注解的值进行排序(默认得到的个数为0):
    • 创建WebSecurity实例对象;
    • 将得到的所有SecurityConfigurer<Filter, WebSecurity>类型Bean通过webSecurity的apply方法添加到集合中;
    • 开始初始化定义的名为springSecurityFilterChain的Filter类型的Bean(FilterChainProxy):
      在初始化的过程中,首先判断收集的SecurityConfigurer<Filter, WebSecurity>类型的Bean的个数是否为0,如果为0则使用默认的WebSecurityConfigurerAdapter作为安全的配置,并也通过apply方法添加到WebSecurity的集合中,接着执行WebSecurity的build()方法完成创建;

      WebSecurityConfigurerAdapter中创建了一个SecurityConfigurer类型的HttpSecurity实例来真正完成安全的装配,HttpSecurity也提供了很多方法来方便对各种安全机制的配置;

  3. WebSecurity的build()方法通过调用doBuild()方法来执行各个装配的过程方法:beforeInit()、init()、beforeConfigure()、configure()与performBuild();这几个方法的调用过程也就是执行WebSecurity中各个SecurityConfigurer的这几个方法的过程;

  4. WebSecurity的performBuild()方法执行后便返回了FilterChainProxy实例,该实例里面保存了每个SecurityConfigurer所生成的SecurityFilterChain(也就是说,一个SecurityConfigurer装配执行完后会得到一个SecurityFilterChain对象);SecurityFilterChain(DefaultSecurityFilterChain)实例里面保存了SecurityConfigurer中装配的各个Filter对象(List<Filter>)以及进行路径匹配验证的RequestMatcher实例对象。

    安全配置和验证过程本质就是Filter的创建以及对请求拦截的处理过程

  5. 最后在spring-boot-autoconfigure/META-INF/spring.factories里面定义的启动类SecurityFilterAutoConfiguration中创建DelegatingFilterProxyRegistrationBean委托注册实例,该实例委托的正是上面创建的名为springSecurityFilterChain的Filter,也就是FilterChainProxy。通过该委托将springSecurityFilterChain在Servlet容器启动的时候添加到Filter链中。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容