Spring Security 啟動過程分析

TigerJin發表於2021-09-09

以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@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這個配置類,如下圖:

圖片描述

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;



作者:iHelin
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2144/viewspace-2818772/,如需轉載,請註明出處,否則將追究法律責任。

相關文章