Spring secutiry 源码解析 (一) —— 认证流程

本系列源码分析是基于springboot 2.1.3版本, springcloud Greenwich.RELEASE 版本。 当前springsecurity 版本 5.1.4

当我们新建一个配置类加上 @EnableWebSecurity 就开始了 spring security 的安全认证流程。看这里注解干了些什么:

@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
@EnableGlobalAuthentication

引入了三个类和使用了另一个 @EnableGlobalAuthentication 注解,日常使用呢,就看这个 WebSecurityConfiguration 类,打开 IDEA 的 Structure视图,能看到个概览:

WebSecurityConfiguration 类图概览

我们在没有这类安全验证框架时,都是在Servlet的拦截器里拦截用户请求,在spring的时代,用的就是filter,在Spring Security里面他就是用的filter chain来做安全认证的。我们这里可以找到一个springSecurityFilterChain方法,看源码:

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = webSecurityConfigurers != null
        && !webSecurityConfigurers.isEmpty();
    if (!hasConfigurers) {
        WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
            .postProcess(new WebSecurityConfigurerAdapter() {
            });
        webSecurity.apply(adapter);
    }
    return webSecurity.build();
}

这里如果你的配置类实现了 WebSecurityConfigurerAdapter ,那么 webSecurityConfigurers list就会有你自定义的那个实现类, 重点看 webSecurity.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");
}

这是 AbstractSecurityBuilder 类里面的方法, building 这个变量默认就是false的安全实现:

private AtomicBoolean building = new AtomicBoolean();

然后执行 dobuild 方法, 这个方法的实现类是在 AbstractConfiguredSecurityBuilder 里面:

@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;
    }
}

build 流程一看就出来了 init ------>> configure ------->> performBuild ,接下去往下剖析:

private void init() throws Exception {
    Collection> configurers = getConfigurers();

    for (SecurityConfigurer configurer : configurers) {
        configurer.init((B) this);
    }

    for (SecurityConfigurer configurer : configurersAddedInInitializing) {
        configurer.init((B) this);
    }
}

O泛型表示Filter类, B 泛型表示WebSecurity类, 这里的 configurers 就是你自己实现的那个配置类了(继承自 WebSecurityConfigurerAdapter ), 执行 init 方法,一般我们不会实现init方法,所以看的是 WebSecurityConfigurerAdapterinit 方法:

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 (http != null) {
        return http;
    }
    
    DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
        .postProcess(new DefaultAuthenticationEventPublisher());
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    
    AuthenticationManager authenticationManager = authenticationManager();
    authenticationBuilder.parentAuthenticationManager(authenticationManager);
    authenticationBuilder.authenticationEventPublisher(eventPublisher);
    Map, Object> sharedObjects = createSharedObjects();

    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                            sharedObjects);
    if (!disableDefaults) {
       
        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();
       
        ClassLoader classLoader = this.context.getClassLoader();
        List defaultHttpConfigurers =
            SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

        for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
            http.apply(configurer);
        }
    }
    configure(http);
    return http;
}

disableDefaults 默认初始化为false, 可以看到这里对httpSecurity做默认的配置,最后调用 configure(http) 方法, 这里调用的就是我们一般都会实现的那个方法了,这样我们的自定义配置就起作用了. 再回过去看doBuildconfigure()方法, 调用的就是我们自定义实现:

@Override
public void configure(WebSecurity web) throws Exception {
    super.configure(web);
}

重点来看performBuild方法:

@Override
    protected Filter performBuild() throws Exception {
        Assert.state(
            !securityFilterChainBuilders.isEmpty(),
            () -> "At least one SecurityBuilder 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 securityFilterChains = new ArrayList<>(
            chainSize);
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        for (SecurityBuilder 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;
    }

这个方法,构建了 securityFilterChains 并添加了一些 filter 进去:

Filters

好,那么大致流程我们已经知道了, 那么问题又出现了, 这些 filter 是怎么加进去的呢? 其实是在httpSecurity的对象配置时候加进去的:

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();

随便点开一个方法,看其源码:

public CsrfConfigurer csrf() throws Exception {
    ApplicationContext context = getContext();
    return getOrApply(new CsrfConfigurer<>(context));
}

public FormLoginConfigurer formLogin() throws Exception {
    return getOrApply(new FormLoginConfigurer<>());
}

发现都是getOrApply方法来实现, 该方法内部又是 apply 方法实现的, 继续看:

public > C apply(C configurer)
            throws Exception {
    configurer.addObjectPostProcessor(objectPostProcessor);
    configurer.setBuilder((B) this);
    add(configurer);
    return configurer;
}

里面有个add方法, 将该 configurer 加入到 AbstractConfiguredSecurityBuilder 抽象类的 configurers 变量中去, 然后我们抽取个别 configrurer

public final class DefaultLoginPageConfigurer ... {
    
    @Override
    public void init(H http) throws Exception {
        Function> hiddenInputs = request -> {
            CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
            if (token == null) {
                return Collections.emptyMap();
            }
            return Collections.singletonMap(token.getParameterName(), token.getToken());
        };
        this.loginPageGeneratingFilter.setResolveHiddenInputs(hiddenInputs);
        this.logoutPageGeneratingFilter.setResolveHiddenInputs(hiddenInputs);
        http.setSharedObject(DefaultLoginPageGeneratingFilter.class,
                loginPageGeneratingFilter);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void configure(H http) throws Exception {
        AuthenticationEntryPoint authenticationEntryPoint = null;
        ExceptionHandlingConfigurer exceptionConf = http
                .getConfigurer(ExceptionHandlingConfigurer.class);
        if (exceptionConf != null) {
            authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint();
        }

        if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
            loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
            http.addFilter(loginPageGeneratingFilter);
            http.addFilter(this.logoutPageGeneratingFilter);
        }
    }
}
    

dobuild 方法的 几个方法类似, 在这里一个会将filter加载进来, 所以说每个 configurer 其实就是filter的包装. 这一节就说这些东西,下一节将分析具体的 filter 流程.

你可能感兴趣的:(Spring secutiry 源码解析 (一) —— 认证流程)