daisuzz.log

HttpSecurityはどうやってSecurityFilterChainを生成しているのか

タイトルの通り、Spring Securityで認証認可を行う場合に利用するHttpSecurityの処理について調べたので備忘録として書いていきます。

前置き

Spring Securityの認証認可の設定方法

Spring Securityで認証認可の設定を行う場合、XMLやJavaConfigの形式で設定内容を書いていきます。 JavaConfigの場合、例えば以下のようなコードを書きます。 ※ 以下はKotlinで書いたものになっています。

@Configuration
class SecurityConfiguration {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            authorizeRequests {
                authorize(anyRequest, authenticated)
            }
            formLogin { }
            logout {
                logoutUrl = "/logout"
                logoutSuccessUrl = "/"
            }
        }
        return http.build()
    }
}

引数として受け取っているHttpSecurityに対して設定を行い、最後にbuild()を呼ぶことでSecurityFilterChainのインスタンスを生成し、Beanとして登録しています。 SecurityFilterChainについては、以下ドキュメントを参考にしてください。

https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-securityfilterchain

上記のサンプルコードでは、KotlinDSLを使って認証をかけるURLを指定したり、認証方法としてフォーム認証を有効化したり、ログアウト時のURLを指定しています。

HttpSecurityとは?

HttpSecurityは、特定のHTTPリクエストに対して認証認可の設定を行うためのクラスであり、DefaultSecurityFilterChainのBuilderクラスとなっています。

Builderクラスであるため、HttpSecurity#build()を呼ぶとDefaultSecurityFilterChainのインスタンスが生成されます。

以降では、HttpSecurity#build()が実行された場合にどういった処理が行われるのか書いていきます。

HttpSecurity#build()の処理内容

HttpSecurity#build()を呼ぶと、親クラスのAbstractSecurityBuilder#build()が実行されます。

https://github.com/spring-projects/spring-security/blob/006b9b960797d279b31cf8c8d16f1549c5632b2c/config/src/main/java/org/springframework/security/config/annotation/AbstractSecurityBuilder.java#L35-L42

    @Override
    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#build()では、すでにメソッドが別のところで呼ばれていればAlreadyBuiltExceptionをthrowし、呼ばれていなければAbstractSecurityBuilder#doBuild()が実行されます。

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java#L295-L309

   @Override
    protected final O doBuild() throws Exception {
        synchronized (this.configurers) {
            this.buildState = BuildState.INITIALIZING;
            beforeInit();
            init();
            this.buildState = BuildState.CONFIGURING;
            beforeConfigure();
            configure();
            this.buildState = BuildState.BUILDING;
            O result = performBuild();
            this.buildState = BuildState.BUILT;
            return result;
        }
    }

AbstractSecurityBuilder#doBuild()の具体的な処理はAbstractConfiguredSecurityBuilder#doBuild()で定義されています。 AbstractConfiguredSecurityBuilder#doBuild()では、以下5つの処理が実行されます。

  • beforeInit()
  • init()
  • beforeConfigure()
  • configure()
  • performBuild()

beforeInit()

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java#L311-L317

   protected void beforeInit() throws Exception {
    }

HttpSecurityでは何も実行されません。

init()

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java#L334-L343

   @SuppressWarnings("unchecked")
    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 : this.configurersAddedInInitializing) {
            configurer.init((B) this);
        }
    }

AbstractConfiguredSecurityBuilderに設定されたSecurityConfigurerの一覧を取得して、それぞれのSecurityConfigurerの初期化処理(SecurityConfigurer#init())を実行します。

SecurityConfigurerとは、SecurityBuilderを設定するためのクラスです。 例えばSpring SecurityのJavaConfigにhttp.formLogin()という設定をおこなうと、FormLoginConfigurerというSecurityConfigurerがAbstractConfiguredSecurityBuilderにセットされます。

FormLoginConfigurerの場合、init()が呼ばれると、値が指定されていない項目にデフォルトの値を登録します。

beforeConfigure()

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java#L2668-L2676

   @Override
    protected void beforeConfigure() throws Exception {
        if (this.authenticationManager != null) {
            setSharedObject(AuthenticationManager.class, this.authenticationManager);
        }
        else {
            setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
        }
    }

AuthenticationManagerがHttpSecurityに設定されていればそのAuthenticationManagerをAbstractConfiguredSecurityBuilder.sharedObjectsというHashMapにセットします。

HttpSecurityにAuthenticationManagerが設定されていなければAuthenticationManagerBuilderを使ってデフォルトのAuthenticationManagerをHashMapにセットします。

ここでセットしたAuthenticationManagerが、Spring Securityの認証を行うFilterクラスにセットされます。 例えば、独自の認証用Filterを作成する際には、ここでセットしたAuthenticationManagerをAbstractConfiguredSecurityBuilder#getSharedObject()を使って取得して、独自の認証用Filterにセットします。

configure()

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java#L345-L351

   @SuppressWarnings("unchecked")
    private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }

AbstractConfiguredSecurityBuilderに設定されたSecurityConfigurerの一覧を取得して、それぞれのSecurityConfigurerの設定処理(SecurityConfigurer#configure())を実行します。

performBuild()

https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java#L2678-L2693

   @SuppressWarnings("unchecked")
    @Override
    protected DefaultSecurityFilterChain performBuild() {
        ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
                ExpressionUrlAuthorizationConfigurer.class);
        AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
        boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
        Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
                "authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
        this.filters.sort(OrderComparator.INSTANCE);
        List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
        for (Filter filter : this.filters) {
            sortedFilters.add(((OrderedFilter) filter).filter);
        }
        return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
    }

HttpSecurityに設定されたFilter一覧をソートして、その結果を元にDefaultSecurityFilterChainインスタンスを生成します。

ここで生成されたインスタンスがHttpSecurity#build()の戻り値として返されます。

参考資料

This entry is released under version 2.0 of the Apache License.