9.1. Spring Security概要


Spring Securityは、アプリケーションにセキュリティ対策機能を実装する際に使用するフレームワークである。
Spring Securityはスタンドアロンなアプリケーションでも利用できるが、サーブレットコンテナにデプロイするWebアプリケーションに対してセキュリティ対策を行う際に利用するのが一般的である。
本章では、Spring Securityが提供する機能のうち、一般的なWebアプリケーションでの利用頻度が高いと思われる機能にしぼって説明する。
Spring Securityは、本ガイドラインで紹介していない機能も多く提供している。
Spring Securityが提供するすべての機能を知りたい場合は、Spring Security Reference -Servlet Applications-を参照されたい。

9.1.1. Spring Securityの機能

9.1.1.1. セキュリティ対策の基本機能

Spring Securityは、セキュリティ対策の基本機能として以下の機能を提供している。

セキュリティ対策の基本機能

機能

説明

認証機能

アプリケーションを利用するユーザーの正当性を確認する機能。

認可機能

アプリケーションが提供するリソースや処理に対してアクセスを制御する機能。


9.1.1.2. セキュリティ対策の強化機能

Spring Securityでは認証と認可という基本的な機能に加え、Webアプリケーションのセキュリティを強化するための機能をいくつか提供している。

セキュリティ対策の強化機能

機能

説明

セッション管理機能

セッションハイジャック攻撃やセッション固定攻撃からユーザーを守る機能、 セッションのライフサイクル(生成、破棄、タイムアウト)を制御するための機能。

CSRF対策機能

クロスサイトリクエストフォージェリ(CSRF)攻撃からユーザーを守るための機能。

セキュリティヘッダ出力機能

Webブラウザのセキュリティ対策機能と連携し、ブラウザの機能を悪用した攻撃からユーザーを守るための機能。


9.1.2. Spring Securityのアーキテクチャ

各機能の詳細な説明を行う前に、Spring Securityのアーキテクチャ概要とSpring Securityを構成する主要なコンポーネントの役割を説明する。

Note

ここで説明する内容は、Spring Securityが提供するデフォルトの動作をそのまま利用する場合や、Spring Securityのコンフィギュレーションをサポートする仕組みを利用する場合は、開発者が直接意識する必要ない。

そのため、まず各機能の使い方を知りたい場合は、本節を読み飛ばしても問題はない。

ただし、ここで説明する内容は、Spring Securityのデフォルトの動作をカスタマイズする際に必要になるので、アプリケーションのアーキテクトは一読しておくことを推奨する。


9.1.2.1. Spring Securityのモジュール

まずフレームワークスタックとなっているSpring Securityの提供モジュールを紹介する。


9.1.2.1.1. フレームワークスタックモジュール群

フレームワークスタックモジュールは、以下の通りである。
本ガイドラインでもこれらのモジュールを使用してセキュリティ対策を行う方法について説明する。
フレームワークスタックモジュール群

モジュール名

説明

spring-security-core

認証と認可機能を実現するために必要となるコアなコンポーネントが格納されている。 このモジュールに含まれるコンポーネントは、スタンドアロン環境で実行するアプリケーションでも使用することができる。

spring-security-web

Webアプリケーションのセキュリティ対策を実現するために必要となるコンポーネントが格納されている。 このモジュールに含まれるコンポーネントは、Web層(サーブレットAPIなど)に依存する処理を行う。

spring-security-config

各モジュールから提供されているコンポーネントのセットアップをサポートするためのコンポーネント(コンフィギュレーションをサポートするクラスやXMLネームスペースを解析するクラスなど)が格納されている。 このモジュールを使用すると、Spring Securityのbean定義を簡単に行うことができる。

spring-security-taglibs

認証情報や認可機能にアクセスするためのJSPタグライブラリが格納されている。

spring-security-acl

EntityなどのドメインオブジェクトをAccess Control List(ACL)を使用して認可制御するために必要となるコンポーネントが格納されている。 本モジュールは依存関係の都合上、フレームワークスタックに含まれているモジュールであるため、本ガイドラインにおいて使用方法の説明は行わない。

thymeleaf-extras-springsecurity6

認証情報や認可機能にアクセスするためのThymeleafのダイアレクトが格納されている。


9.1.2.1.2. 要件に合わせて使用するモジュール群

フレームワークスタックではないが、一般的に利用される認証方法などをサポートするために、以下のようなモジュールも提供されている。
セキュリティ要件に応じて、これらのモジュールの使用も検討されたい。
要件に合わせて使用するモジュール群

モジュール名

説明

spring-security-remoting

JNDI経由でDNSにアクセス、Basic認証が必要なWebサイトにアクセス、Spring Securityを使用してセキュリティ対策しているメソッドにRMI経由でアクセスする際に必要となるコンポーネントが格納されている。

spring-security-aspects

AspectJを使用してJavaのメソッドに認可機能を適用する際、必要となるコンポーネントが格納されている。 このモジュールは、AOPとしてSpring AOPを使う場合は不要である。

spring-security-messaging[3]

SpringのWeb Socket機能に対してセキュリティ対策を追加するためのコンポーネントが格納されている。

spring-security-data[3]

Spring Dataの機能から認証情報にアクセスできるようにするためのコンポーネントが格納されている。

spring-security-ldap

Lightweight Directory Access Protocol(LDAP)を使用した認証を実現するために必要となるコンポーネントが格納されている。

spring-security-openid

OpenID[1]を使用した認証を実現するために必要となるコンポーネントが格納されている。

spring-security-cas

Central Authentication Service(CAS)[2]と連携するために必要となるコンポーネントが格納されている。

spring-security-crypto

暗号化、キーの生成、ハッシュアルゴリズムを利用したパスワードエンコーディングを行うためのコンポーネントが格納されている。 このモジュールに含まれるクラスは、フレームワークスタックモジュールであるspring-security-coreにも含まれている。


9.1.2.1.3. テスト用のモジュール

Spring Security 4.0からはテストを支援するためのモジュールが追加されている。

テスト用のモジュール

モジュール名

説明

spring-security-test[3]

Spring Securityに依存しているクラスのテストを支援するためのコンポーネントが格納されている。
このモジュールを使用すると、JUnitテスト時に必要となる認証情報を簡単にセットアップすることができる。
また、Spring MVCのテスト用コンポーネント(MockMvc)と連携して使用するコンポーネントも含まれている。


9.1.2.2. フレームワーク処理

Spring Securityは、サーブレットフィルタの仕組みを使用してWebアプリケーションのセキュリティ対策を行うアーキテクチャを採用しており、以下のような流れで処理を実行している。

../_images/Architecture.png

Spring Securityのフレームワークアーキテクチャ

項番

説明

(1)

クライアントは、Webアプリケーションに対してリクエストを送る。

(2)

Spring SecurityのFilterChainProxyクラス(サーブレットフィルタ)がリクエストを受け取り、 HttpFirewallインタフェースのメソッドを呼び出してHttpServletRequestHttpServletResponseに対してファイアウォール機能を組み込む。

(3)

FilterChainProxyクラスは、Spring Securityが提供しているセキュリティ対策用のSecurity Filter(サーブレットフィルタ)クラスに処理を委譲する。

(4)

Security Filterは複数のクラスで構成されており、サーブレットフィルタの処理が正常に終了すると後続のサーブレットフィルタが呼び出される。

(5)

最後のSecurity Filterの処理が正常に終了した場合、後続処理(サーブレットフィルタやサーブレットなど)を呼びだし、Webアプリケーション内のリソースへアクセスする。

(6)

FilterChainProxyクラスは、Webアプリケーションから返却されたリソースをクライアントへレスポンスする。


Webアプリケーション向けのフレームワーク処理を構成する主要なコンポーネントは以下の通りである。

9.1.2.2.1. FilterChainProxy

FilterChainProxyクラスは、Webアプリケーション向けのフレームワーク処理のエントリーポイントとなるサーブレットフィルタクラスである。
このクラスはフレームワーク処理の全体の流れを制御するクラスであり、具体的なセキュリティ対策処理はSecurity Filterに委譲している。

9.1.2.2.2. HttpFirewall

HttpFirewallインタフェースは、HttpServletRequestHttpServletResponseに対してファイアウォール機能を組み込むためのインタフェースである。
デフォルトでは、StrictHttpFirewallクラスが使用され、ディレクトリトラバーサル攻撃やHTTPレスポンス分割攻撃に対するチェックなどが実装されている。

Note

Spring Security 5.0.1, 4.2.4, 4.1.5より、デフォルトで使用されるHttpFirewallインタフェースの実装クラスはDefaultHttpFirewallからStrictHttpFirewallへ変更された。

DefaultHttpFirewallRFC 2396に基づきリクエストURLの正規化を行うことで悪意あるURLを拒否するが、StrictHttpFirewallはより厳密にURLを構成する文字に不正な値がないことをチェックし、悪意あるURLを拒否する。これにより、認証認可のバイパスやReflected File Download(RFD)攻撃への対策がなされている。

URLの正規化は脆弱性対策としては不十分であるため、従来通りDefaultHttpFirewallを利用するように変更することは推奨しない。また、StrictHttpFirewallのチェックについても、一部カスタマイズ可能なパラメータも存在するが、脆弱性の原因となりうるため変更することは推奨しない。

StrictHttpFirewallの詳細については、Javadocを参照されたい。


9.1.2.2.3. SecurityFilterChain

SecurityFilterChainインタフェースは、FilterChainProxyが受け取ったリクエストに対して、適用するSecurity Filterのリストを管理するためのインタフェースである。 デフォルトではDefaultSecurityFilterChainクラスが使用され、適用するSecurity Filterのリストを、リクエストURLのパターン毎に管理する。

たとえば、以下のようなbean定義を行うと、URLに応じて異なる内容のセキュリティ対策を適用することができる。

  • xxx-web/src/main/xxxx/yyyy/zzzz/config/web/SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChainApi(HttpSecurity http) throws Exception {
    http.securityMatcher(new AntPathRequestMatcher("/api/**")); // (1)
    // omitted
    return http.build();
}

@Bean
public SecurityFilterChain filterChainUi(HttpSecurity http) throws Exception {
    http.securityMatcher(new AntPathRequestMatcher("/ui/**"));
    // omitted
    return http.build();
}

項番

説明

(1)
HttpSecurity#securityMatcherメソッドを呼び出し、SecurityFilterChainを適用するRequestMatcherオブジェクトを指定する。HttpSecurity#securityMatcherメソッドを呼び出さない場合は、”/**“がパスパターンとして使用される。
指定可能なパスパターンはRequestMatcherに依存しており、ブランクプロジェクトのデフォルト設定で使用されるAntPathRequestMatcherではAnt形式の記述が利用できる。
また、RequestMatcherオブジェクトではなくパスパターンを直接指定することも可能であるが、MvcRequestMatcherが適用されるため、本ガイドラインではAntPathRequestMatcherを指定する。詳しくはパスパターンの解析に利用する仕組みの設定についてを参照されたい。

Tip

パスパターンの解析に利用する仕組みの設定について

Spring SecurityはSpring MVCと同時に使用する場合、パス解析にはデフォルトでMvcRequestMatcherが使用される。ただし、MvcRequestMatcherを使用するためにはSpring Securityが案内する方法に従い、Spring MVCとSpring Securityを同一コンテキスト(DIコンテナ)内に設定する必要がある。

TERASOLUNA Server Framework for Java (5.x)では以下のような懸念からAntPathRequestMatcher(必要に応じてRegexRequestMatcher)の使用を前提としている。

  • RESTful Web Serviceで必要となるSpring MVCのコンポーネントを有効化するための設定SpringMvcRestConfig.javaのように、SpringMvcConfig.javaの設定バリエーションを1つのアプリケーション内に複数持つ必要がある場合、SpringMvcConfig.javaに相当するファイルごとにアプリケーションコンテキスト(DIコンテナ)を分け、複数のServletにそれぞれを割り当てる必要があり、 MvcRequestMatcherを使用できる条件を満たさない。また、設定方法によっては、MvcRequestMatcherを利用するとSpring MVCと連携せずに動作するという意図しない動作となり、脆弱性に繋がる。

  • MvcRequestMatcherでは、Spring MVCがHandlerMappingやハンドラ(ハンドラメソッド等)を決定する時と同じ処理をパターンマッチングの度に行う(SpringSecurityConfig.javaに記載したpattern毎に実施される)ため、1リクエストあたりの処理コストが増加しやすいと推測される。特に、RESTで使用するパス変数のように、パスの条件にワイルドカードを持つハンドラメソッドを呼び出す際は、「Spring MVCがHandlerMappingやハンドラ(ハンドラメソッド等)を決定する」部分の処理コストがアプリケーションコンテキスト(DIコンテナ)内のハンドラメソッドの定義量に大きく依存するため、アプリケーションコンテキスト(DIコンテナ)を1つにまとめる構造では、性能上の懸念がある。


9.1.2.2.4. Security Filter

Security Filterクラスは、フレームワーク機能やセキュリティ対策機能を実現する上で必要となる処理を提供するサーブレットフィルタクラスである。

Spring Securityは、複数のSecurity Filterを連鎖させることでWebアプリケーションのセキュリティ対策を行う仕組みになっている。
ここでは、認証と認可機能を実現するために必要となるコアなクラスを紹介する。
詳細は Spring Security Reference -Security Filters-を参照されたい。
コアなSecurity Filter

クラス名

説明

SecurityContextHolderFilter

認証情報についてリクエストを跨いで共有するための処理を提供するクラス。

Note

Spring Security Reference -Security Filters-には記載がないが、Spring Security 5.7.0より非推奨となったSecurityContextPersistenceFilterの代替クラスである。

UsernamePasswordAuthenticationFilter

リクエストパラメータで指定されたユーザー名とパスワードを使用して認証処理を行うクラス。 HttpSessionSecurityContextRepositoryに認証情報を格納することで、リクエストを跨いで認証情報を共有している。 フォーム認証を行う際に使用する。

LogoutFilter

ログアウト処理を行うクラス。

AuthorizationFilter

HTTPリクエスト(HttpServletRequest)に対して認可処理を実行するためのクラス。

Warning

web.xmlの<filter-mapping>には業務要件に応じて適切なDispatcherTypeを設定すること

ブランクプロジェクトのデフォルト設定では、web.xmlの<filter-mapping>/<dispatcher>には明示的に設定を行っていないため、REQUESTのみを対象として認可処理が実行される。

業務要件に応じてweb.xmlの<filter-mapping>/<dispatcher>REQUEST+ FORWARD等、ディスパッチ先でもFilterが動作するように設定するように留意すること。また、REQUEST以外を設定する場合、REQUESTも含めて動作することを期待する場合は、REQUESTも含めて設定する必要があることに注意すること。

ExceptionTranslationFilter

AuthorizationFilterで発生した例外をハンドリングし、クライアントへ返却するレスポンスを制御するクラス。
デフォルトの実装では、未認証ユーザーからのアクセスの場合は認証を促すレスポンス、認証済みのユーザーからのアクセスの場合は認可エラーを通知するレスポンスを返却する。

9.1.3. Spring Securityのセットアップ

WebアプリケーションにSpring Securityを適用するためのセットアップ方法について説明する。

ここでは、WebアプリケーションにSpring Securityを適用し、Spring Securityが提供しているデフォルトのログイン画面を表示させる最もシンプルなセットアップ方法を説明する。
実際のアプリケーション開発で必要となるカスタマイズ方法や拡張方法については、次節以降で順次説明する。

Note

開発プロジェクトをブランクプロジェクトから作成すると、ここで説明する各設定はセットアップ済みの状態になっている。

開発プロジェクトの作成方法については、「Webアプリケーション向け開発プロジェクトの作成」を参照されたい。


9.1.3.1. 依存ライブラリの適用

まず、Spring Securityを依存関係として使用している共通ライブラリを適用する。
Spring Securityと共通ライブラリの関連については、共通ライブラリの構成要素を参照されたい。

本ガイドラインでは、Mavenを使って開発プロジェクトを作成していることを前提とする。

  • xxx-domain/pom.xmlの設定例

<dependency>
    <groupId>org.terasoluna.gfw</groupId>
    <artifactId>terasoluna-gfw-security-core</artifactId>  <!-- (1) -->
</dependency>
  • xxx-web/pom.xmlの設定例

<dependency>
    <groupId>org.terasoluna.gfw</groupId>
    <artifactId>terasoluna-gfw-security-web</artifactId>  <!-- (2) -->
</dependency>

項番

説明

(1)

ドメイン層のプロジェクトでSpring Securityの機能を使用する場合は、terasoluna-gfw-security-coreをdependencyに追加する。

(2)

アプリケーション層のプロジェクトでSpring Securityの機能を使用する場合は、terasoluna-gfw-security-webをdependencyに追加する。

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。


9.1.3.2. bean定義ファイルの作成

Spring Securityのコンポーネントをbean定義するため、以下のようなXMLファイルを作成する。(ブランクプロジェクトより抜粋)

  • xxx-web/src/main/xxxx/yyyy/zzzz/config/web/SpringSecurityConfig.javaの定義例

@Configuration
@EnableWebSecurity // (10)
public class SpringSecurityConfig {

    // (2)
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/resources/**"));
    }

    // (3)
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.formLogin(Customizer.withDefaults()); // (4)
        http.logout(Customizer.withDefaults()); // (5)
        http.exceptionHandling(ex -> ex
                .accessDeniedHandler(accessDeniedHandler())); // (6)
        http.addFilterAfter(
                userIdMDCPutFilter(), AnonymousAuthenticationFilter.class); // (7)
        http.sessionManagement(Customizer.withDefaults()); // (8)

        return http.build();
    }

    // (11)
    @Bean("accessDeniedHandler")
    public AccessDeniedHandler accessDeniedHandler() {
        LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> errorHandlers = new LinkedHashMap<>();

        AccessDeniedHandlerImpl invalidCsrfTokenErrorHandler = new AccessDeniedHandlerImpl();
        invalidCsrfTokenErrorHandler.setErrorPage("/WEB-INF/views/common/error/invalidCsrfTokenError.jsp");
        errorHandlers.put(InvalidCsrfTokenException.class, invalidCsrfTokenErrorHandler);

        AccessDeniedHandlerImpl missingCsrfTokenErrorHandler = new AccessDeniedHandlerImpl();
        missingCsrfTokenErrorHandler.setErrorPage("/WEB-INF/views/common/error/missingCsrfTokenError.jsp");
        errorHandlers.put(MissingCsrfTokenException.class, missingCsrfTokenErrorHandler);

        AccessDeniedHandlerImpl defaultErrorHandler = new AccessDeniedHandlerImpl();
        defaultErrorHandler.setErrorPage("/WEB-INF/views/common/error/accessDeniedError.jsp");

        return new DelegatingAccessDeniedHandler(errorHandlers, defaultErrorHandler);
    }

    // (12)
    @Bean("webSecurityExpressionHandler")
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        return new DefaultWebSecurityExpressionHandler();
    }

    // (13)
    @Bean("userIdMDCPutFilter")
    public UserIdMDCPutFilter userIdMDCPutFilter() {
        return new UserIdMDCPutFilter();
    }
}

項番

説明

(2)
WebSecurityCustomizerをBean定義し、セキュリティ対策が不要なリソースパスの設定を行う。
(3)
SecurityFilterChainをBean定義する。
(4)
HttpSecurity#formLoginを実装し、フォーム認証を使用したログインに関する設定を行う。
詳細はフォーム認証を参照されたい。
(5)
HttpSecurity#logoutを実装し、ログアウトに関する設定を行う。
詳細はログアウトを参照されたい。
(6)
HttpSecurity#exceptionHandlingを実装し、アクセスエラー時の制御を行うための設定を定義する。
(7)
ログ出力するユーザ情報をMDCに格納するための共通ライブラリのフィルタを定義する。
(8)
HttpSecurity#sessionManagementタグ を定義し、セッション管理に関する設定を行う。
詳細はセッション管理を参照されたい。
(10)
@EnableWebSecurityアノテーションを設定して、認証機能用のコンポーネントをbean定義する。このアノテーションを定義しておかないとサーバ起動時にエラーが発生する。
(11)
アクセスエラー時のエラーハンドリングを行うコンポーネントをbean定義する。
(12)
画面項目で認可処理を行うハンドラをbean定義する。
(13)
ログ出力するユーザ情報をMDCにする共通ライブラリのコンポーネントをbean定義する。
  • xxx-web/src/main/xxxx/yyyy/zzzz/config/web/SpringMvcConfig.javaの定義例(抜粋)

@Bean("templateEngine")
public SpringTemplateEngine templateEngine() {
    SpringTemplateEngine bean = new SpringTemplateEngine();
    bean.setEnableSpringELCompiler(true);
    bean.setTemplateResolver(templateResolver());
    Set<IDialect> set = new HashSet<>();
    set.add(new SpringSecurityDialect()); // (1)
    bean.setAdditionalDialects(set);
    return bean;
}

項番

説明

(1)

TemplateEngineに、thymeleaf-extras-springsecurity6が提供するダイアレクト(SpringSecurityDialect) を利用する定義を追加する。


作成したbean定義ファイルを使用してSpringのDIコンテナを生成するように定義する。

  • xxx-web/src/main/webapp/WEB-INF/web.xmlの設定例

<!-- (1) -->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>
<!-- (2) -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        xxxx.yyyy.zzzz.config.app.ApplicationContextConfig
        xxxx.yyyy.zzzz.config.web.SpringSecurityConfig
    </param-value>
</context-param>

項番

説明

(1)

サーブレットコンテナのリスナクラスとして、ContextLoaderListenerクラスを指定する。

(2)

サーブレットコンテナのcontextConfigLocationパラメータに、ApplicationContextConfig.javaに加えて、Spring Security用のbean定義クラスを追加する。


9.1.3.3. サーブレットフィルタの設定

最後に、Spring Securityが提供しているサーブレットフィルタクラス(FilterChainProxy) をサーブレットコンテナに登録する。

  • xxx-web/src/main/webapp/WEB-INF/web.xmlの設定例

<!-- (1) -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>
<!-- (2) -->
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

項番

説明

(1)

Spring Frameworkから提供されているDelegatingFilterProxyを使用して、 SpringのDIコンテナで管理されているbean(FilterChainProxy)をサーブレットコンテナに登録する。 サーブレットフィルタの名前には、SpringのDIコンテナで管理されているbeanのbean名(springSecurityFilterChain)を指定する。

(2)

Spring Securityを適用するURLのパターンを指定する。 上記例では、すべてのリクエストに対してSpring Securityを適用する。


9.1.3.4. セキュリティ対策を適用しないため設定

セキュリティ対策が不要なリソースのパス(cssファイルやimageファイルにアクセスするためのパスなど)に対しては、WebSecurityCustomizer(または<sec:http>タグ)を使用して、Spring Securityのセキュリティ機能(Security Filter)が適用されないように制御することができる。

  • xxx-web/src/main/xxxx/yyyy/zzzz/SpringSecurityConfig.javaの定義例

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return web -> web.ignoring().requestMatchers(
            new AntPathRequestMatcher("/resources/**")); // (1)(2)
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    return http.build();
}

項番

説明

(1)
AntPathRequestMatcherにセキュリティ機能を適用しないパスのパターンを指定する。
(2)
WebSecurity#ignoringを呼び出し、Spring Securityのセキュリティ機能(Security Filter)が適用されないように設定する。