10. 認証

10.1 インメモリ認証

1 人のユーザーに対してメモリ内認証を構成する例はすでに見ました。以下は、複数のユーザーを構成する例です。

@Bean
public UserDetailsService userDetailsService() throws Exception {
    // ensure the passwords are encoded properly
    UserBuilder users = User.withDefaultPasswordEncoder();
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(users.username("user").password("password").roles("USER").build());
    manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
    return manager;
}

10.2 JDBC 認証

JDBC ベースの認証をサポートする更新を見つけることができます。以下の例では、アプリケーション内で DataSource をすでに定義していることを前提としています。jdbc-javaconfig: GitHub (英語) サンプルは、JDBC ベースの認証の完全な使用例を提供します。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // ensure the passwords are encoded properly
    UserBuilder users = User.withDefaultPasswordEncoder();
    auth
        .jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser(users.username("user").password("password").roles("USER"))
            .withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}

10.3 LDAP 認証

10.3.1 概要

LDAP は、多くの場合、組織がユーザー情報の中央リポジトリとして、および認証サービスとして使用します。また、アプリケーションユーザーのロール情報を保存するためにも使用できます。

Spring Security の LDAP プロバイダーを完全に構成できるように、LDAP サーバーの構成方法にはさまざまなシナリオがあります。認証とロールの取得に別々の戦略インターフェースを使用し、さまざまな状況を処理するように構成できるデフォルトの実装を提供します。

Spring Security で使用する前に、LDAP に精通している必要があります。次のリンクは、関連する概念の優れた導入と、フリーの LDAP サーバー OpenLDAP を使用してディレクトリを設定するためのガイドです: http://www.zytrax.com/books/ldap/ (英語) 。Java から LDAP にアクセスするために使用される JNDI API に精通していることも役立つ場合があります。LDAP プロバイダーではサードパーティの LDAP ライブラリ(Mozilla、JLDAP など)は使用していませんが、Spring LDAP が広く使用されているため、独自のカスタマイズを追加する予定がある場合は、そのプロジェクトにある程度精通していると便利です。

LDAP 認証を使用する場合、LDAP 接続プールを適切に構成することが重要です。これを行う方法を知っている未知の場合は、Java LDAP ドキュメント: Oracle (英語) を参照できます。

10.3.2 Spring Security での LDAP の使用

Spring Security の LDAP 認証は、大まかに次の段階に分けることができます。

  • ログイン名から一意の LDAP「識別名」または DN を取得します。これは、多くの場合、ユーザー名と DN の正確なマッピングが事前にわかっていない限り、ディレクトリ内で検索を実行することを意味します。そのため、ユーザーはログイン時に「joe」という名前を入力する場合がありますが、LDAP への認証に使用される実際の名前は、uid=joe,ou=users,dc=spring,dc=io などの完全な DN になります。
  • そのユーザーとして「バインド」するか、DN のディレクトリエントリのパスワード属性に対してユーザーのパスワードのリモート「比較」操作を実行することにより、ユーザーを認証します。
  • ユーザーの権限のリストをロードします。

例外は、LDAP ディレクトリがユーザー情報を取得してローカルで認証するために使用されている場合です。これは、ユーザーパスワードなどの属性に対する読み取りアクセスが制限されてディレクトリが設定されることが多いため、不可能な場合があります。

以下にいくつかの構成シナリオを見ていきます。使用可能な構成オプションの詳細については、セキュリティ名前空間スキーマ(XML エディターで使用可能な情報)を参照してください。

10.4 LDAP サーバーの構成

最初に行う必要があるのは、認証が行われるサーバーを構成することです。これは、セキュリティ名前空間の <ldap-server> 要素を使用して行われます。これは、url 属性を使用して、外部 LDAP サーバーを指すように構成できます。

<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
[Note] メモ

spring-security は、組み込み LDAP サーバーとして apacheds および unboundid との統合を提供します。ldap-server の属性 mode を使用して、それらの中から選択できます。

10.4.1 組み込みテストサーバーの使用

<ldap-server> 要素を使用して組み込みサーバーを作成することもできます。これは、テストやデモンストレーションに非常に役立ちます。この場合、url 属性なしで使用します。

<ldap-server root="dc=springframework,dc=org"/>

ここでは、ディレクトリのルート DIT がデフォルトである「dc = springframework、dc = org」であることを指定しました。このように使用すると、ネームスペースパーサーは埋め込み Apache ディレクトリサーバーを作成し、サーバーにロードしようとする LDIF ファイルのクラスパスをスキャンします。ロードする LDIF リソースを定義する ldif 属性を使用して、この動作をカスタマイズできます。

<ldap-server ldif="classpath:users.ldif" />

これにより、外部サーバーで常に作業するのが不便になる可能性があるため、LDAP を簡単に起動および実行できます。また、Apache Directory サーバーの接続に必要な複雑な Bean 構成からユーザーを隔離します。プレーンな Spring Bean を使用すると、構成がはるかに煩雑になります。アプリケーションで使用するために必要な Apache Directory 依存関係 jar が必要です。これらは、LDAP サンプルアプリケーションから取得できます。

10.4.2 バインド認証の使用

これは最も一般的な LDAP 認証シナリオです。

<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>

この単純な例では、指定されたパターンでユーザーのログイン名を置き換え、そのユーザーとしてログインパスワードをバインドしようとすることで、ユーザーの DN を取得します。すべてのユーザーがディレクトリ内の単一ノードに保存されている場合、これは問題ありません。代わりに、ユーザーを見つけるために LDAP 検索フィルターを構成する場合は、次を使用できます。

<ldap-authentication-provider user-search-filter="(uid={0})"
        user-search-base="ou=people"/>

上記のサーバー定義で使用すると、user-search-filter 属性の値をフィルターとして使用して DN ou=people,dc=springframework,dc=org で検索を実行します。この場合も、ユーザー名はフィルター名のパラメーターの代わりに使用されるため、uid 属性がユーザー名と等しいエントリを検索します。user-search-base が提供されない場合、検索はルートから実行されます。

10.4.3 権限の読み込み

LDAP ディレクトリ内のグループから権限をロードする方法は、次の属性によって制御されます。

  • group-search-base。グループ検索を実行するディレクトリツリーの部分を定義します。
  • group-role-attribute。グループエントリによって定義された機関の名前を含む属性。デフォルトは cn
  • group-search-filter。グループメンバーシップの検索に使用されるフィルター。デフォルトは uniqueMember={0} で、groupOfUniqueNames LDAP クラス [2] に対応しています。この場合、置換されたパラメーターはユーザーの完全な識別名です。ログイン名でフィルタリングする場合は、パラメーター {1} を使用できます。

次の構成を使用した場合

<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"
        group-search-base="ou=groups" />

ユーザー「ben」として正常に認証されると、その後の権限の読み込みは、ディレクトリエントリ ou=groups,dc=springframework,dc=org で検索を実行し、値 uid=ben,ou=people,dc=springframework,dc=org の属性 uniqueMember を含むエントリを探します。デフォルトでは、機関名には接頭辞 ROLE_ が付加されます。これは、role-prefix 属性を使用して変更できます。プレフィックスが不要な場合は、role-prefix="none" を使用します。ロード機関の詳細については、DefaultLdapAuthoritiesPopulator クラスの Javadoc を参照してください。

10.5 実装クラス

上記で使用した名前空間設定オプションは、Spring Bean を明示的に使用するよりも使いやすく、はるかに簡潔です。アプリケーションコンテキストで Spring Security LDAP を直接構成する方法を知る必要がある場合があります。たとえば、一部のクラスの動作をカスタマイズできます。名前空間の設定を使用して満足している場合は、このセクションと次のセクションをスキップできます。

メインの LDAP プロバイダークラスである LdapAuthenticationProvider は、実際にはあまり機能しませんが、ユーザーを認証し、ユーザーの GrantedAuthority のセットをそれぞれ取得する LdapAuthenticator と LdapAuthoritiesPopulator の 2 つの他の Bean に作業を委譲します。

10.5.1 LdapAuthenticator の実装

オーセンティケーターは、必要なユーザー属性を取得する責任もあります。これは、属性に対するアクセス許可が使用されている認証の種類に依存する可能性があるためです。例: ユーザーとしてバインドする場合は、ユーザー自身の権限で読み取る必要がある場合があります。

現在、Spring Security には 2 つの認証戦略が用意されています。

  • LDAP サーバーへの直接認証(「バインド」認証)。
  • パスワード比較。ユーザーが指定したパスワードがリポジトリに保存されているパスワードと比較されます。これは、パスワード属性の値を取得してローカルで確認するか、指定されたパスワードが比較のためにサーバーに渡され、実際のパスワード値が取得されない LDAP「比較」操作を実行することで実行できます。

共通の機能

(いずれかの方法で)ユーザーを認証する前に、アプリケーションに提供されたログイン名から識別名(DN)を取得する必要があります。これは、単純なパターンマッチング(setUserDnPatterns 配列プロパティの設定)または userSearch プロパティの設定によって実行できます。DN パターンマッチングアプローチでは、標準の Java パターン形式が使用され、パラメーター {0} の代わりにログイン名が使用されます。パターンは、構成された SpringSecurityContextSource がバインドする DN に関連している必要があります(この詳細については、LDAP サーバーへの接続に関するセクションを参照してください)。例: URL ldap://monkeymachine.co.uk/dc=springframework,dc=org で LDAP サーバーを使用しており、パターン uid={0},ou=greatapes がある場合、「gorilla」のログイン名は DN uid=gorilla,ou=greatapes,dc=springframework,dc=org にマップされます。一致が見つかるまで、構成された各 DN パターンが順番に試行されます。検索の使用については、以下の検索オブジェクトに関するセクションを参照してください。2 つのアプローチの組み合わせも使用できます。最初にパターンがチェックされ、一致する DN が見つからない場合は検索が使用されます。

BindAuthenticator

パッケージ org.springframework.security.ldap.authentication のクラス BindAuthenticator は、バインド認証戦略を実装します。ユーザーとしてバインドしようとするだけです。

PasswordComparisonAuthenticator

クラス PasswordComparisonAuthenticator は、パスワード比較認証戦略を実装しています。

10.5.2 LDAP サーバーへの接続

上記の Bean は、サーバーに接続できる必要があります。両方に Spring LDAP の ContextSource の拡張である SpringSecurityContextSource を提供する必要があります。特別な要件がない限り、通常 DefaultSpringSecurityContextSource Bean を構成します。これは、LDAP サーバーの URL と、オプションでサーバーにバインドするときにデフォルトで使用される「マネージャー」ユーザーのユーザー名とパスワードで構成できます(匿名でバインドする代わりに)。詳細については、このクラスおよび Spring LDAP の AbstractContextSource の Javadoc を参照してください。

10.5.3 LDAP 検索オブジェクト

多くの場合、ディレクトリ内のユーザーエントリを見つけるには、単純な DN マッチングよりも複雑な戦略が必要です。これを LdapUserSearch インスタンスにカプセル化して、オーセンティケーターの実装に提供して、ユーザーがユーザーを見つけられるようにすることができます。提供される実装は FilterBasedLdapUserSearch です。

FilterBasedLdapUserSearch

この Bean は、LDAP フィルターを使用して、ディレクトリ内のユーザーオブジェクトを照合します。このプロセスは、JDK DirContext クラス (英語) の対応する検索メソッドの Javadoc で説明されています。そこで説明したように、検索フィルターにはパラメーターを指定できます。このクラスの場合、有効なパラメーターは {0} のみで、これはユーザーのログイン名に置き換えられます。

10.5.4 LdapAuthoritiesPopulator

ユーザーを正常に認証した後、LdapAuthenticationProvider は、構成された LdapAuthoritiesPopulator Bean を呼び出すことにより、ユーザーの一連の権限をロードしようとします。DefaultLdapAuthoritiesPopulator は、ユーザーがメンバーになっているグループをディレクトリで検索することにより、権限をロードする実装です(通常、これらはディレクトリ内の groupOfNames または groupOfUniqueNames エントリになります)。動作の詳細については、このクラスの Javadoc を参照してください。

LDAP を認証のみに使用し、異なるソース(データベースなど)から機関をロードする場合は、このインターフェースの独自の実装を提供し、代わりにそれを挿入できます。

10.5.5 Spring Bean の構成

ここで説明したいくつかの Bean を使用した一般的な構成は、次のようになります。

<bean id="contextSource"
        class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
    <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
    <property name="password" value="password"/>
</bean>

<bean id="ldapAuthProvider"
        class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <constructor-arg>
        <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <constructor-arg ref="contextSource"/>
            <property name="userDnPatterns">
                <list><value>uid={0},ou=people</value></list>
            </property>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
            <constructor-arg ref="contextSource"/>
            <constructor-arg value="ou=groups"/>
            <property name="groupRoleAttribute" value="ou"/>
        </bean>
    </constructor-arg>
</bean>

これにより、プロバイダーが URL ldap://monkeymachine:389/dc=springframework,dc=org で LDAP サーバーにアクセスするように設定されます。認証は、DN uid=<user-login-name>,ou=people,dc=springframework,dc=org とのバインドを試行することにより実行されます。認証が成功すると、DN ou=groups,dc=springframework,dc=org でデフォルトのフィルター (member=<user’s-DN>) で検索することにより、ユーザーにロールが割り当てられます。ロール名は、各マッチの "ou" 属性から取得されます。

DN パターンの代わりに(またはそれに加えて)フィルター (uid=<user-login-name>) を使用するユーザー検索オブジェクトを構成するには、次の Bean を構成します。

<bean id="userSearch"
        class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
    <constructor-arg index="0" value=""/>
    <constructor-arg index="1" value="(uid={0})"/>
    <constructor-arg index="2" ref="contextSource" />
</bean>

BindAuthenticator Bean の userSearch プロパティを設定して使用します。オーセンティケータは、このユーザーとしてバインドを試みる前に、検索オブジェクトを呼び出して正しいユーザーの DN を取得します。

10.5.6 LDAP 属性とカスタマイズされた UserDetails

LdapAuthenticationProvider を使用した認証の最終的な結果は、標準 UserDetailsService インターフェースを使用した通常の Spring Security 認証と同じです。UserDetails オブジェクトが作成され、返された Authentication オブジェクトに保存されます。UserDetailsService を使用する場合と同様に、一般的な要件は、この実装をカスタマイズして追加のプロパティを追加できることです。LDAP を使用する場合、これらは通常、ユーザーエントリの属性になります。UserDetails オブジェクトの作成は、プロバイダーの UserDetailsContextMapper 戦略によって制御されます。これは、LDAP コンテキストデータとの間でユーザーオブジェクトをマッピングするロールを果たします。

public interface UserDetailsContextMapper {

    UserDetails mapUserFromContext(DirContextOperations ctx, String username,
            Collection<GrantedAuthority> authorities);

    void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}

最初の方法のみが認証に関連しています。このインターフェースの実装を提供し、それを LdapAuthenticationProvider に注入すると、UserDetails オブジェクトの作成方法を正確に制御できます。最初のパラメーターは Spring LDAP の DirContextOperations のインスタンスで、認証中にロードされた LDAP 属性へのアクセスを提供します。username パラメーターは認証に使用される名前であり、最後のパラメーターは、構成された LdapAuthoritiesPopulator によってユーザーにロードされた権限のコレクションです。

コンテキストデータのロード方法は、使用している認証の種類によって若干異なります。BindAuthenticator では、バインド操作から返されたコンテキストを使用して属性を読み取ります。それ以外の場合、構成済みの ContextSource から取得した標準コンテキストを使用してデータを読み取ります(ユーザーを見つけるための検索が構成されている場合、これはデータになります。検索オブジェクトによって返されます)。

10.6 Active Directory 認証

Active Directory は独自の非標準の認証オプションをサポートしており、通常の使用パターンは標準の LdapAuthenticationProvider にはあまり合いません。通常、認証は、LDAP 識別名を使用するのではなく、ドメインユーザー名([email protected] (英語) 形式)を使用して実行されます。これを簡単にするために、Spring Security 3.1 には、一般的な Active Directory セットアップ用にカスタマイズされた認証プロバイダーがあります。

10.6.1 ActiveDirectoryLdapAuthenticationProvider

ActiveDirectoryLdapAuthenticationProvider の構成は非常に簡単です。ドメイン名と、サーバー [3] のアドレスを提供する LDAP URL を提供するだけです。設定例は次のようになります。

<bean id="adAuthenticationProvider"
        class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <constructor-arg value="mydomain.com" />
    <constructor-arg value="ldap://adserver.mydomain.com/" />
</bean>

サーバーの場所を定義するために別の ContextSource を指定する必要がないことに注意してください -Bean は完全に自己完結型です。たとえば、「Sharon」という名前のユーザーは、ユーザー名 sharon または完全な Active Directory userPrincipalName、つまり [email protected] (英語) を入力することで認証できます。ユーザーのディレクトリエントリが検索され、作成された UserDetails オブジェクトのカスタマイズで使用できるように属性が返されます(上記のように、この目的で UserDetailsContextMapper を挿入できます)。ディレクトリとのすべての対話は、ユーザー自身の ID を使用して行われます。「マネージャー」ユーザーという概念はありません。

デフォルトでは、ユーザー権限はユーザーエントリの memberOf 属性値から取得されます。ユーザーに割り当てられた権限は、UserDetailsContextMapper を使用して再度カスタマイズできます。GrantedAuthoritiesMapper をプロバイダーインスタンスに挿入して、最終的に Authentication オブジェクトになる機関を制御することもできます。

Active Directory エラーコード

デフォルトでは、失敗した結果は標準 Spring Security BadCredentialsException を引き起こします。プロパティ convertSubErrorCodesToExceptions を true に設定すると、例外メッセージが解析され、Active Directory 固有のエラーコードを抽出し、より具体的な例外を発生させようとします。詳細については、クラス Javadoc を確認してください。

10.7 LDAP Java 構成

LDAP ベースの認証をサポートする更新を見つけることができます。ldap-javaconfig: GitHub (英語) サンプルは、LDAP ベースの認証を使用した完全な例を提供します。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .ldapAuthentication()
            .userDnPatterns("uid={0},ou=people")
            .groupSearchBase("ou=groups");
}

上記の例では、次の LDIF と組み込みの Apache DS LDAP インスタンスを使用しています。

users.ldif。

dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

10.8 AuthenticationProvider

10.8.1 AuthenticationProvider Java 構成

カスタム AuthenticationProvider を Bean として公開することにより、カスタム認証を定義できます。例: 以下は、SpringAuthenticationProvider が AuthenticationProvider を実装すると仮定して認証をカスタマイズします。

[Note] メモ

これは、AuthenticationManagerBuilder が読み込まれていない場合にのみ使用されます

@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
    return new SpringAuthenticationProvider();
}

10.8.2 AuthenticationProvider XML 設定

実際には、アプリケーションコンテキストファイルにいくつかの名前を追加するよりも、よりスケーラブルなユーザー情報のソースが必要になります。ほとんどの場合、ユーザー情報をデータベースや LDAP サーバーなどに保存する必要があります。LDAP 名前空間の構成は LDAP の章で処理されるため、ここでは説明しません。アプリケーションコンテキストに「myUserDetailsService」と呼ばれる Spring Security の UserDetailsService のカスタム実装がある場合、これを使用して認証できます

<authentication-manager>
    <authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

データベースを使用する場合は、次を使用できます。

<authentication-manager>
<authentication-provider>
    <jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>

「securityDataSource」は、アプリケーションコンテキストの DataSource Bean の名前で、標準 Spring Security ユーザーデータテーブルを含むデータベースを指します。あるいは、Spring Security JdbcDaoImpl Bean を構成し、user-service-ref 属性を使用してそれを指すこともできます。

<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

<beans:bean id="myUserDetailsService"
    class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>

次のように標準 AuthenticationProvider Bean を使用することもできます

<authentication-manager>
    <authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>

myAuthenticationProvider は、AuthenticationProvider を実装するアプリケーションコンテキストの Bean の名前です。複数の authentication-provider 要素を使用できます。その場合、プロバイダーは宣言された順序で照会されます。ネームスペースを使用して Spring Security AuthenticationManager を構成する方法の詳細については、セクション 10.11: “ 認証マネージャーと名前空間 ” を参照してください。

10.9 UserDetailsService

カスタム UserDetailsService を Bean として公開することにより、カスタム認証を定義できます。例: 以下は、SpringDataUserDetailsService が UserDetailsService を実装すると仮定して認証をカスタマイズします。

[Note] メモ

これは、AuthenticationManagerBuilder が設定されておらず、AuthenticationProviderBean が定義されていない場合にのみ使用されます。

@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
    return new SpringDataUserDetailsService();
}

PasswordEncoder を Bean として公開することにより、パスワードのエンコード方法をカスタマイズすることもできます。例: bcrypt を使用する場合、以下に示すように Bean 定義を追加できます。

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

10.10 パスワードエンコーディング

Spring Security の PasswordEncoder インターフェースは、パスワードの安全な保存を可能にするために、パスワードの一方向変換を実行するために使用されます。PasswordEncoder は一方向の変換であるため、パスワード変換を双方向にする必要がある場合(つまり、データベースへの認証に使用される資格情報を保存する場合)は意図されていません。通常、PasswordEncoder は、認証時にユーザーが指定したパスワードと比較する必要があるパスワードを保存するために使用されます。

10.10.1 パスワード履歴

長年にわたって、パスワードを保存するための標準的なメカニズムが進化してきました。最初は、パスワードはプレーンテキストで保存されていました。パスワードは、データストアにアクセスするために必要な資格情報で保存されているため、パスワードは安全であると想定されていました。しかし、悪意のあるユーザーは、SQL インジェクションなどの攻撃を使用して、ユーザー名とパスワードの大きな「データダンプ」を取得する方法を見つけることができました。ますます。多くのユーザー資格情報がパブリックセキュリティの専門家になるにつれて、ユーザーのパスワードを保護するためにより多くのことを行う必要があることに気付きました。

開発者は、SHA-256 などの一方向ハッシュを介してパスワードを実行した後、パスワードを保存することが推奨されました。ユーザーが認証を試行すると、ハッシュされたパスワードは、入力したパスワードのハッシュと比較されます。つまり、システムはパスワードの一方向ハッシュを保存するだけで済みました。違反が発生した場合、パスワードの一方向ハッシュのみが公開されました。ハッシュは一方向であり、ハッシュが与えられたパスワードを推測することは計算上困難であったため、システム内の各パスワードを把握する努力は価値がありません。この新しいシステムを無効にするために、悪意のあるユーザーはレインボーテーブル (英語) として知られるルックアップテーブルを作成することにしました。各パスワードを毎回推測する作業を行うのではなく、パスワードを一度計算してルックアップテーブルに保存しました。

レインボーテーブルの有効性を緩和するために、開発者はソルトパスワードを使用することが推奨されます。ハッシュ関数への入力としてパスワードだけを使用する代わりに、ランダムなバイト(ソルトと呼ばれる)がすべてのユーザーのパスワードに対して生成されます。ソルトとユーザーのパスワードは、一意のハッシュを生成するハッシュ関数を介して実行されます。ソルトは、ユーザーのパスワードとともにクリアテキストで保存されます。次に、ユーザーが認証を試みると、ハッシュされたパスワードは、保存されたソルトのハッシュと入力したパスワードと比較されます。独自のソルトは、ソルトとパスワードの組み合わせごとにハッシュが異なるため、レインボーテーブルが効果的ではなくなったことを意味します。

現代では、暗号化ハッシュ(SHA-256 など)はもはや安全ではないことがわかります。その理由は、最新のハードウェアを使用すると、1 秒間に何十億ものハッシュ計算を実行できるからです。これは、各パスワードを簡単に個別に解読できることを意味します。

開発者は、適応型一方向機能を利用してパスワードを保存することが推奨されています。適応型一方向機能によるパスワードの検証は、意図的にリソース(CPU、メモリなど)を集中的に使用します。適応型一方向機能により、ハードウェアが向上するにつれて成長する「作業要素」を構成できます。システムのパスワードを確認するのに約 1 秒かかるように「作業要素」を調整することをお勧めします。このトレードオフは、攻撃者がパスワードを解読することを困難にすることですが、それほど高負荷ではなく、あなた自身のシステムに過度の負担をかけます。Spring Security は「作業要素」の適切な出発点を提供しようとしましたが、パフォーマンスはシステムによって大きく異なるため、ユーザーは自分のシステムの「作業要素」をカスタマイズすることをお勧めします。使用する必要がある適応型一方向関数の例には、bcrypt (英語) PBKDF2 (英語) scrypt (英語) 、および Argon2 (英語) が含まれます。

適応型一方向機能は意図的にリソースを集中的に使用するため、すべてのリクエストに対してユーザー名とパスワードを検証すると、アプリケーションのパフォーマンスが大幅に低下します。検証リソースを集中的に使用することでセキュリティが得られるため、Spring Security(または他のライブラリ)がパスワードの検証を高速化するためにできることはありません。ユーザーは、長期資格情報(つまり、ユーザー名とパスワード)を短期資格情報(つまり、セッション、OAuth トークンなど)と交換することをお勧めします。セキュリティを損なうことなく、短期間の資格情報を迅速に検証できます。

10.10.2 DelegatingPasswordEncoder

Spring Security 5.0 以前は、デフォルトの PasswordEncoder は NoOpPasswordEncoder で、プレーンテキストのパスワードが必要でした。パスワード履歴セクションに基づいて、デフォルトの PasswordEncoder が BCryptPasswordEncoder のようになっていることを期待するかもしれません。ただし、これは 3 つの実際の問題を無視します。

  • 簡単に移行できない古いパスワードエンコーディングを使用する多くのアプリケーションがあります
  • パスワード保存のベストプラクティスは再び変更されます。
  • フレームワーク Spring Security が頻繁に重大な変更を加えることができないため

代わりに、Spring Security は DelegatingPasswordEncoder を導入します。

  • 現在のパスワードストレージの推奨事項を使用してパスワードが確実にエンコードされるようにする
  • 最新およびレガシー形式のパスワードの検証を許可する
  • 将来的にエンコーディングをアップグレードできるようにする

PasswordEncoderFactories を使用して DelegatingPasswordEncoder のインスタンスを簡単に構築できます。

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();

または、独自のカスタムインスタンスを作成することもできます。例:

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);

パスワード保存形式

パスワードの一般的な形式は次のとおりです。

{id}encodedPassword

id は、どの PasswordEncoder を使用すべきかを調べるために使用される識別子であり、encodedPassword は選択された PasswordEncoder の元のエンコードされたパスワードです。id はパスワードの先頭にあり、{ で始まり } で終わる必要があります。id が見つからない場合、id は null になります。例: 以下は、異なる id を使用してエンコードされたパスワードのリストです。元のパスワードはすべて「password」です。

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 1
{noop}password 2
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 3
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  4
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 5

1

最初のパスワードは、PasswordEncoder id が bcrypt で、encodedPassword が $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG になります。一致すると、BCryptPasswordEncoder に委譲されます。

2

2 番目のパスワードには、noop の PasswordEncoder id と password の encodedPassword があります。一致すると、NoOpPasswordEncoder に委譲されます。

3

3 番目のパスワードの PasswordEncoder id は pbkdf2 で、encodedPassword は 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc です。一致すると、Pbkdf2PasswordEncoder に委譲されます。

4

4 番目のパスワードは、PasswordEncoder id が scrypt で、encodedPassword が $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= になります。一致すると、SCryptPasswordEncoder に委譲されます。

5

最終パスワードには、sha256 の PasswordEncoder id と 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 の encodedPassword があります。一致すると、StandardPasswordEncoder に委譲されます。

[Note] メモ

一部のユーザーは、ストレージ形式が潜在的なハッカーに提供されていることを懸念する場合があります。パスワードの保存はアルゴリズムがシークレットであることに依存していないため、これは懸念事項ではありません。さらに、攻撃者がプレフィックスなしで簡単に把握できる形式はほとんどあります。例: BCrypt パスワードは、多くの場合 $2a$ で始まります。

パスワードエンコーディング

コンストラクターに渡される idForEncode は、パスワードのエンコードに使用される PasswordEncoder を決定します。上記で作成した DelegatingPasswordEncoder では、password をエンコードした結果が BCryptPasswordEncoder に委譲され、接頭辞として {bcrypt} が付けられます。最終結果は次のようになります。

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

パスワード照合

マッチングは、{id} と、コンストラクターで提供される id から PasswordEncoder へのマッピングに基づいて行われます。「パスワード保存形式」の例は、これがどのように行われるかの実例を提供します。デフォルトでは、パスワードとマッピングされていない id (nullID を含む)で matches(CharSequence, String) を呼び出した結果は IllegalArgumentException になります。この動作は、DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder) を使用してカスタマイズできます。

id を使用することにより、任意のパスワードエンコーディングを照合できますが、最新のパスワードエンコーディングを使用してパスワードをエンコードします。暗号化とは異なり、パスワードハッシュはプレーンテキストを回復する簡単な方法がないように設計されているため、これは重要です。平文を復元する方法がないため、パスワードの移行が難しくなります。ユーザーが NoOpPasswordEncoder を移行するのは簡単ですが、開始時の操作を簡単にするためにデフォルトで含めることを選択しました。

はじめに

デモやサンプルをまとめる場合、ユーザーのパスワードをハッシュするのに時間がかかるのは少し面倒です。これを簡単にする便利なメカニズムがありますが、これはまだ本番用ではありません。

User user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

複数のユーザーを作成している場合は、ビルダーを再利用することもできます。

UserBuilder users = User.withDefaultPasswordEncoder();
User user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
User admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();

これは保存されているパスワードをハッシュしますが、パスワードはメモリとコンパイルされたソースコードで公開されます。本番環境ではまだ安全とは見なされません。本番環境では、パスワードを外部でハッシュする必要があります。

トラブルシューティング

「パスワード保存形式」で説明されているように、 保存されているパスワードの 1 つに id がない場合、次のエラーが発生します。

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

エラーを解決する最も簡単な方法は、パスワードのエンコードに使用する PasswordEncoder を明示的に提供するように切り替えることです。これを解決する最も簡単な方法は、パスワードが現在どのように格納されているかを把握し、正しい PasswordEncoder を明示的に提供することです。Spring Security 4.2.x から移行する場合は、NoOpPasswordEncoder Bean を公開することにより、以前の動作に戻すことができます。例: Java 構成を使用している場合は、次のような構成を作成できます。

[Warning] 警告

NoOpPasswordEncoder に戻すことは安全とは見なされません。代わりに、DelegatingPasswordEncoder の使用に移行して、安全なパスワードエンコーディングをサポートする必要があります。

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

XML 構成を使用している場合、passwordEncoder という ID を持つ PasswordEncoder を公開できます。

<b:bean id="passwordEncoder"
        class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>

または、すべてのパスワードの前に正しい ID を付けて、引き続き DelegatingPasswordEncoder を使用できます。例: BCrypt を使用している場合、次のようなものからパスワードを移行します。

$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

マッピングの完全なリストについては、PasswordEncoderFactories (Javadoc) の Javadoc を参照してください。

10.10.3 BCryptPasswordEncoder

BCryptPasswordEncoder 実装は、広くサポートされている bcrypt (英語) アルゴリズムを使用してパスワードをハッシュします。パスワードクラッキングに対する耐性を高めるために、bcrypt は意図的に遅くなります。他の適応型一方向機能と同様に、システムのパスワードを確認するのに約 1 秒かかるように調整する必要があります。

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

10.10.4 Argon2PasswordEncoder

Argon2PasswordEncoder 実装は、Argon2 (英語) アルゴリズムを使用してパスワードをハッシュします。Argon2 はパスワードハッシュコンペティション (英語) の勝者です。カスタムハードウェアでのパスワードクラッキングを無効にするために、Argon2 は大量のメモリを必要とする意図的に遅いアルゴリズムです。他の適応型一方向機能と同様に、システムのパスワードを確認するのに約 1 秒かかるように調整する必要があります。Argon2PasswordEncoder が BouncyCastle を必要とする場合の現在の実装。

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

10.10.5 Pbkdf2PasswordEncoder

Pbkdf2PasswordEncoder 実装は、PBKDF2 (英語) アルゴリズムを使用してパスワードをハッシュします。パスワードクラッキングを無効にするため、PBKDF2 は意図的に遅いアルゴリズムです。他の適応型一方向機能と同様に、システムのパスワードを確認するのに約 1 秒かかるように調整する必要があります。このアルゴリズムは、FIPS 認定が必要な場合に適しています。

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

10.10.6 SCryptPasswordEncoder

SCryptPasswordEncoder 実装は、暗号化 (英語) アルゴリズムを使用してパスワードをハッシュします。カスタムハードウェアでのパスワードクラッキングを無効にするために、scrypt は大量のメモリを必要とする故意に遅いアルゴリズムです。他の適応型一方向機能と同様に、システムのパスワードを確認するのに約 1 秒かかるように調整する必要があります。

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

10.10.7 その他 PasswordEncoders

下位互換性のために完全に存在する他の PasswordEncoder 実装が多数あります。これらはすべて、もはや安全であると見なされないことを示すために非推奨です。ただし、既存のレガシーシステムを移行することは難しいため、削除する計画はありません。

10.10.8 パスワードエンコーダーの XML 構成

パスワードは、その目的のために設計された安全なハッシュアルゴリズムを使用して常にエンコードする必要があります(SHA や MD5 などの標準アルゴリズムではありません)。これは、<password-encoder> 要素によってサポートされています。bcrypt でエンコードされたパスワードを使用すると、元の認証プロバイダーの構成は次のようになります。

<beans:bean name="bcryptEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<authentication-manager>
<authentication-provider>
    <password-encoder ref="bcryptEncoder"/>
    <user-service>
    <user name="jimi" password="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
            authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
            authorities="ROLE_USER" />
    </user-service>
</authentication-provider>
</authentication-manager>

bcrypt は、異なるアルゴリズムの使用を強制するレガシーシステムがない限り、ほとんどの場合に適しています。単純なハッシュアルゴリズムを使用している場合、さらに悪いことに、プレーンテキストパスワードを保存している場合は、bcrypt などのより安全なオプションへの移行を検討する必要があります。

10.11 認証マネージャーと名前空間

Spring Security で認証サービスを提供するメインインターフェースは AuthenticationManager です。通常、これは Spring Security の ProviderManager クラスのインスタンスです。これは、以前にフレームワークを使用したことがある場合はすでによく知っているかもしれません。そうでない場合は、後で技術概要の章で説明します。Bean インスタンスは、authentication-manager 名前空間要素を使用して登録されます。名前空間で HTTP またはメソッドセキュリティを使用している場合は、カスタム AuthenticationManager を使用できませんが、使用する AuthenticationProvider を完全に制御できるため、これは問題になりません。

追加の AuthenticationProvider Bean を ProviderManager に登録すると、ref 属性を持つ <authentication-provider> 要素を使用して登録できます。属性の値は、追加するプロバイダー Bean の名前です。例:

<authentication-manager>
<authentication-provider ref="casAuthenticationProvider"/>
</authentication-manager>

<bean id="casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
</bean>

別の一般的な要件は、コンテキスト内の別の Bean が AuthenticationManager への参照を必要とする可能性があることです。AuthenticationManager のエイリアスを簡単に登録し、アプリケーションコンテキストの他の場所でこの名前を使用できます。

<security:authentication-manager alias="authenticationManager">
...
</security:authentication-manager>

<bean id="customizedFormLoginFilter"
    class="com.somecompany.security.web.CustomFormLoginFilter">
<property name="authenticationManager" ref="authenticationManager"/>
...
</bean>

10.12 セッション管理

HTTP セッションに関連する機能は、フィルターが委譲する SessionManagementFilter と SessionAuthenticationStrategy インターフェースの組み合わせによって処理されます。典型的な使用箇所には、セッション固定保護攻撃防止、セッションタイムアウトの検出、および認証済みユーザーが同時に開くことができるセッション数の制限が含まれます。

10.12.1 タイムアウトの検出

Spring Security を構成して、無効なセッション ID の送信を検出し、ユーザーを適切な URL にリダイレクトできます。これは、session-management 要素によって実現されます。

<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>

このメカニズムを使用してセッションタイムアウトを検出した場合、ユーザーがログアウトしてからブラウザーを閉じずに再度ログインすると、誤ってエラーが報告される可能性があることに注意してください。これは、セッションを無効にしてもセッション Cookie がクリアされず、ユーザーがログアウトしても再送信されるためです。ログアウトハンドラーで次の構文を使用するなどして、ログアウト時に JSESSIONID Cookie を明示的に削除できる場合があります。

<http>
<logout delete-cookies="JSESSIONID" />
</http>

残念ながら、これはすべてのサーブレットコンテナーで動作することを保証できないため、環境でテストする必要があります。

[Note] メモ

=== プロキシの背後でアプリケーションを実行している場合は、プロキシサーバーを構成してセッション Cookie を削除することもできます。例: Apache HTTPD の mod_headers を使用すると、次のディレクティブは、ログアウトリクエストへのレスポンスで JSESSIONID Cookie を期限切れにして削除します(アプリケーションがパス /tutorial にデプロイされていると仮定します)。

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>

===

10.12.2 同時セッション制御

アプリケーションにログインするための 1 人のユーザの機能に制約を課したい場合、Spring Security は以下の簡単な追加を行うことで、これをすぐにサポートします。まず、Spring Security がセッションライフサイクルイベントについて最新の状態に保つために、以下のリスナーを web.xml ファイルに追加する必要があります。

<listener>
<listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

次に、アプリケーションコンテキストに次の行を追加します。

<http>
...
<session-management>
    <concurrency-control max-sessions="1" />
</session-management>
</http>

これにより、ユーザーが複数回ログインするのを防ぐことができます。2 回目のログインでは、最初のログインが無効になります。多くの場合、2 回目のログインを禁止することをお勧めします。その場合

<http>
...
<session-management>
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

2 回目のログインは拒否されます。「拒否」とは、フォームベースのログインが使用されている場合、ユーザーが authentication-failure-url に送信されることを意味します。2 番目の認証が「remember-me」などの別の非対話型メカニズムを介して行われる場合、「unauthorized」(401) エラーがクライアントに送信されます。代わりにエラーページを使用する場合は、session-authentication-error-url 属性を session-management 要素に追加できます。

フォームベースのログインにカスタマイズされた認証フィルターを使用している場合、同時セッション制御サポートを明示的に構成する必要があります。詳細については、セッション管理の章を参照してください。

10.12.3 セッション固定攻撃保護

セッション固定 (英語) 攻撃は、悪意のある攻撃者がサイトにアクセスしてセッションを作成し、同じセッションでログインするように別のユーザーを誘導する可能性がある潜在的なリスクです(たとえば、セッション識別子をパラメーターとして含むリンクを送信することにより))。Spring Security は、ユーザーがログインするときに新しいセッションを作成するか、セッション ID を変更することにより、これに対して自動的に保護します。この保護が必要ない場合、または他の要件と競合する場合は、<session-management> の session-fixation-protection 属性を使用して動作を制御できます。4 つのオプションがあります

  • none - 何もしないでください。元のセッションは保持されます。
  • newSession - 既存のセッションデータをコピーせずに、新しい「クリーン」セッションを作成します(Spring セキュリティ関連の属性は引き続きコピーされます)。
  • migrateSession - 新しいセッションを作成し、既存のすべてのセッション属性を新しいセッションにコピーします。これは、Servlet 3.0 以前のコンテナーのデフォルトです。
  • changeSessionId - 新しいセッションを作成しないでください。代わりに、サーブレットコンテナー(HttpServletRequest#changeSessionId())によって提供されるセッション固定保護を使用します。このオプションは、Servlet 3.1(Java EE 7)以降のコンテナーでのみ使用可能です。古いコンテナーで指定すると、例外が発生します。これは、Servlet 3.1 以降のコンテナーのデフォルトです。

セッション固定保護が発生すると、SessionFixationProtectionEvent がアプリケーションコンテキストで公開されます。changeSessionId を使用している場合は両方のイベントのためにあなたのコードを聴取した場合、この保護機能は任意 javax.servlet.http.HttpSessionIdListener S が通知される結果、その使用は注意します。追加情報については、セッション管理の章を参照してください。

10.12.4 SessionManagementFilter

SessionManagementFilter は、SecurityContextRepository のコンテンツを SecurityContextHolder の現在のコンテンツと照合して、通常、事前認証や remember-me [4] などの非対話型認証メカニズムによって現在のリクエスト中にユーザーが認証されたかどうかを判断します。リポジトリにセキュリティコンテキストが含まれている場合、フィルターは何もしません。存在せず、スレッドローカル SecurityContext に(非匿名の) Authentication オブジェクトが含まれる場合、フィルターは、スタック内の前のフィルターによって認証されたと見なします。次に、構成された SessionAuthenticationStrategy を呼び出します。

ユーザーが現在認証されていない場合、フィルターは無効なセッション ID がリクエストされたかどうかを確認し(タイムアウトなど)、構成されている InvalidSessionStrategy(設定されている場合)を呼び出します。最も一般的な動作は、固定 URL にリダイレクトすることであり、これは標準実装 SimpleRedirectInvalidSessionStrategy にカプセル化されます。後者は、前述のように、ネームスペースを介して無効なセッション URL を構成するときにも使用されます。

10.12.5 SessionAuthenticationStrategy

SessionAuthenticationStrategy は SessionManagementFilter と AbstractAuthenticationProcessingFilter の両方で使用されるため、たとえば、カスタマイズされたフォームログインクラスを使用している場合は、これらの両方にそれを挿入する必要があります。この場合、名前空間とカスタム Bean を組み合わせた一般的な構成は次のようになります。

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="sessionAuthenticationStrategy" ref="sas" />
    ...
</beans:bean>

<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />

Spring セッションスコープ Bean を含む HttpSessionBindingListener を実装するセッションに Bean を格納している場合、デフォルトの SessionFixationProtectionStrategy を使用すると問題が発生する可能性があることに注意してください。詳細については、このクラスの Javadoc を参照してください。

10.12.6 並行性制御

Spring Security は、プリンシパルが指定された回数を超えて同じアプリケーションに対して同時に認証するのを防ぐことができます。多くの ISV はこれを利用してライセンスを強制しますが、ネットワーク管理者がこの機能を好むのは、ユーザーがログイン名を共有できないようにするためです。たとえば、ユーザー "Batman" が 2 つの異なるセッションから Web アプリケーションにログオンするのを停止できます。以前のログインを期限切れにするか、再度ログインしようとしたときにエラーを報告して、2 回目のログインを防ぐことができます。2 番目の方法を使用している場合、明示的にログアウトしていないユーザー(たとえば、ブラウザーを閉じたばかりのユーザー)は、元のセッションが期限切れになるまで再度ログインできないことに注意してください。

同時実行制御はネームスペースでサポートされているため、最も単純な構成については、以前のネームスペースの章を確認してください。ただし、場合によってはカスタマイズする必要があります。

実装では、ConcurrentSessionControlAuthenticationStrategy と呼ばれる SessionAuthenticationStrategy の特殊バージョンを使用します。

[Note] メモ

以前は、同時認証チェックは ProviderManager によって行われ、ConcurrentSessionController で注入できました。後者は、ユーザーが許可されたセッション数を超えようとしているかどうかをチェックします。ただし、このアプローチでは、HTTP セッションを事前に作成する必要があり、望ましくありません。Spring Security 3 では、ユーザーは最初に AuthenticationManager によって認証され、認証に成功すると、セッションが作成され、別のセッションを開くことが許可されているかどうかのチェックが行われます。

同時セッションのサポートを使用するには、以下を web.xml に追加する必要があります。

<listener>
    <listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

さらに、ConcurrentSessionFilter を FilterChainProxy に追加する必要があります。ConcurrentSessionFilter には、2 つのコンストラクター引数 sessionRegistry が必要です。これは一般に SessionRegistryImpl のインスタンスを指し、sessionInformationExpiredStrategy はセッションの有効期限が切れたときに適用する戦略を定義します。ネームスペースを使用して FilterChainProxy およびその他のデフォルト Bean を作成する構成は、次のようになります。

<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />

<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>

<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
    <beans:list>
    <beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
        <beans:constructor-arg ref="sessionRegistry"/>
        <beans:property name="maximumSessions" value="1" />
        <beans:property name="exceptionIfMaximumExceeded" value="true" />
    </beans:bean>
    <beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
    </beans:bean>
    <beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
        <beans:constructor-arg ref="sessionRegistry"/>
    </beans:bean>
    </beans:list>
</beans:constructor-arg>
</beans:bean>

<beans:bean id="sessionRegistry"
    class="org.springframework.security.core.session.SessionRegistryImpl" />

リスナーを web.xml に追加すると、HttpSession が開始または終了するたびに、ApplicationEvent が Spring ApplicationContext に公開されます。セッションが終了すると SessionRegistryImpl に通知できるため、これは重要です。これがないと、ユーザーは、別のセッションからログアウトしたりタイムアウトしたりしても、セッションの許容量を超えると再びログインできなくなります。

現在認証されているユーザーとそのセッションについて SessionRegistry を照会する

名前空間またはプレーン Bean を使用して同時実行制御を設定すると、アプリケーション内で直接使用できる SessionRegistry への参照が提供されるという便利な副作用があります。ユーザーが持つ可能性のあるセッションについては、インフラストラクチャをセットアップする価値があるかもしれません。maximumSession プロパティを -1 に設定して、無制限のセッションを許可できます。名前空間を使用している場合は、session-registry-alias 属性を使用して内部で作成された SessionRegistry のエイリアスを設定し、独自の Bean に注入できる参照を提供できます。

getAllPrincipals() メソッドは、現在認証されているユーザーのリストを提供します。ユーザーのセッションをリストするには、getAllSessions(Object principal, boolean includeExpiredSessions) メソッドを呼び出して、SessionInformation オブジェクトのリストを返します。SessionInformation インスタンスで expireNow() を呼び出すことにより、ユーザーのセッションを期限切れにすることもできます。ユーザーがアプリケーションに戻ると、ユーザーは続行できなくなります。たとえば、これらのメソッドは管理アプリケーションで役立ちます。詳細については、Javadoc を参照してください。

10.13 Remember-Me 認証

10.13.1 概要

Remember-me 認証または永続ログイン認証とは、セッション間でプリンシパルの ID を記憶できる Web サイトを指します。これは通常、Cookie をブラウザーに送信することで実現されます。Cookie は今後のセッションで検出され、自動ログインが行われます。Spring Security は、これらの操作を実行するために必要なフックを提供し、具体的な remember-me 実装を 2 つ備えています。1 つはハッシュを使用して Cookie ベースのトークンのセキュリティを保持し、もう 1 つはデータベースまたはその他の永続的なストレージメカニズムを使用して、生成されたトークンを保存します。

両方の実装が UserDetailsService を必要とすることに注意してください。UserDetailsService を使用しない認証プロバイダー(LDAP プロバイダーなど)を使用している場合、アプリケーションコンテキストに UserDetailsService Bean が含まれていない限り機能しません。

10.13.2 単純なハッシュベースのトークンアプローチ

このアプローチでは、ハッシュを使用して便利な remember-me 戦略を実現します。基本的に、インタラクティブ認証が成功すると、Cookie がブラウザーに送信されます。Cookie は次のように構成されます。

base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

username:          As identifiable to the UserDetailsService
password:          That matches the one in the retrieved UserDetails
expirationTime:    The date and time when the remember-me token expires, expressed in milliseconds
key:               A private key to prevent modification of the remember-me token

remember-me トークンは、指定された期間のみ有効であり、ユーザー名、パスワード、キーが変更されない限り提供されます。特に、これには、トークンの有効期限が切れるまでキャプチャーされた remember-me トークンがユーザーエージェントから使用できるという潜在的なセキュリティ上の課題があります。これは、ダイジェスト認証の場合と同じ課題です。トークンがキャプチャーされたことをプリンシパルが認識している場合、パスワードを簡単に変更し、課題のすべての remember-me トークンをすぐに無効にすることができます。より重要なセキュリティが必要な場合は、次のセクションで説明するアプローチを使用する必要があります。あるいは、remember-me サービスはまったく使用しないでください。

名前空間の構成に関する章で説明されているトピックに精通している場合は、<remember-me> 要素を追加するだけで記憶情報認証を有効にできます。

<http>
...
<remember-me key="myAppKey"/>
</http>

通常、UserDetailsService は自動的に選択されます。アプリケーションコンテキストに複数ある場合は、user-service-ref 属性で使用する必要があるものを指定する必要があります。値は UserDetailsService Bean の名前です。

10.13.3 永久トークンアプローチ

このアプローチは、http://jaspan.com/improved_persistent_login_cookie_best_practice (英語) にいくつかの小さな変更を加えた [5] に基づいています。名前空間の構成でこのアプローチを使用するには、データソース参照を提供します。

<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>

データベースには、次の SQL(または同等のもの)を使用して作成された persistent_logins テーブルが含まれている必要があります。

create table persistent_logins (username varchar(64) not null,
                                series varchar(64) primary key,
                                token varchar(64) not null,
                                last_used timestamp not null)

10.13.4 Remember-Me インターフェースと実装

Remember-me は UsernamePasswordAuthenticationFilter とともに使用され、AbstractAuthenticationProcessingFilter スーパークラスのフックを介して実装されます。BasicAuthenticationFilter 内でも使用されます。フックは、適切なタイミングで具体的な RememberMeServices を呼び出します。インターフェースは次のようになります。

Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication successfulAuthentication);

メソッドの機能に関する詳細については、Javadoc を参照してください。ただし、この段階では、AbstractAuthenticationProcessingFilter は loginFail() および loginSuccess() メソッドのみを呼び出すことに注意してください。autoLogin() メソッドは、SecurityContextHolder に Authentication が含まれていない場合に、RememberMeAuthenticationFilter によって呼び出されます。このインターフェースは、基礎となる remember-me 実装に認証関連イベントの十分な通知を提供し、候補 Web リクエストに Cookie が含まれていて記憶したい場合は常に実装に委譲します。この設計により、任意の数の remember-me 実装戦略を使用できます。上記で、Spring Security が 2 つの実装を提供することを確認しました。これらを順番に見ていきます。

TokenBasedRememberMeServices

この実装は、セクション 10.13.2: “ 単純なハッシュベースのトークンアプローチ ” で説明されているより単純なアプローチをサポートしています。TokenBasedRememberMeServices は RememberMeAuthenticationProvider によって処理される RememberMeAuthenticationToken を生成します。key は、この認証プロバイダーと TokenBasedRememberMeServices の間で共有されます。さらに、TokenBasedRememberMeServices には、署名の比較のためにユーザー名とパスワードを取得し、正しい GrantedAuthority を含む RememberMeAuthenticationToken を生成できる UserDetailsService が必要です。ユーザーがリクエストした場合に Cookie を無効にするアプリケーションによって、何らかのログアウトコマンドが提供される必要があります。TokenBasedRememberMeServices は Spring Security の LogoutHandler インターフェースも実装しているため、LogoutFilter と併用して Cookie を自動的にクリアすることができます。

remember-me サービスを有効にするためにアプリケーションコンテキストで必要な Bean は次のとおりです。

<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>

<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>

<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>

RememberMeServices 実装を UsernamePasswordAuthenticationFilter.setRememberMeServices() プロパティに追加し、RememberMeAuthenticationProvider を AuthenticationManager.setProviders() リストに追加し、RememberMeAuthenticationFilter を FilterChainProxy に追加することを忘れないでください(通常は UsernamePasswordAuthenticationFilter の直後)。

PersistentTokenBasedRememberMeServices

このクラスは TokenBasedRememberMeServices と同じ方法で使用できますが、トークンを保存するには PersistentTokenRepository でさらに構成する必要があります。2 つの標準実装があります。

  • テストのみを目的とした InMemoryTokenRepositoryImpl
  • トークンをデータベースに保存する JdbcTokenRepositoryImpl

データベーススキーマは、上記のセクション 10.13.3: “ 永久トークンアプローチ ” で説明されています。

10.14 OpenID サポート

名前空間は、通常のフォームベースのログインの代わりに、またはそれに加えて、簡単な変更で OpenID (英語) ログインをサポートします。

<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<openid-login />
</http>

次に、OpenID プロバイダー(myopenid.com など)に自分自身を登録し、ユーザー情報をインメモリ <user-service> に追加する必要があります。

<user name="https://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />

認証のために myopenid.com サイトを使用してログインできるはずです。openid-login 要素の user-service-ref 属性を設定することにより、OpenID を使用する特定の UserDetailsService Bean を選択することもできます。詳細については、認証プロバイダーに関する前のセクションを参照してください。このユーザーデータのセットはユーザーの権限を読み込むためにのみ使用されるため、上記のユーザー構成からパスワード属性を省略していることに注意してください。ランダムなパスワードが内部で生成されるため、このユーザーデータを構成の他の場所で認証ソースとして誤って使用することを防ぎます。

10.14.1 属性交換

OpenID 属性交換 (英語) のサポート。例として、次の構成は、アプリケーションで使用するために、OpenID プロバイダーからメールとフルネームを取得しようとします。

<openid-login>
<attribute-exchange>
    <openid-attribute name="email" type="https://axschema.org/contact/email" required="true"/>
    <openid-attribute name="name" type="https://axschema.org/namePerson"/>
</attribute-exchange>
</openid-login>

各 OpenID 属性の「タイプ」は、特定のスキーマ(この場合は https://axschema.org/ (英語) )によって決定される URI です。認証を成功させるために属性を取得する必要がある場合、required 属性を設定できます。サポートされる正確なスキーマと属性は、OpenID プロバイダーによって異なります。属性値は認証プロセスの一部として返され、次のコードを使用して後でアクセスできます。

OpenIDAuthenticationToken token =
    (OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List<OpenIDAttribute> attributes = token.getAttributes();

OpenIDAttribute には、属性タイプと取得された値(または複数値属性の場合は値)が含まれます。技術概要の章で、Spring Security のコアコンポーネントを見るときに、SecurityContextHolder クラスの使用方法について詳しく説明します。複数の ID プロバイダーを使用する場合は、複数の属性交換構成もサポートされます。各 identifier-matcher 属性を使用して、複数の attribute-exchange 要素を提供できます。これには、ユーザーが指定した OpenID 識別子と照合される正規表現が含まれます。構成例については、コードベースの OpenID サンプルアプリケーションを参照してください。Google、Yahoo、MyOpenID プロバイダーに異なる属性リストが提供されています。

10.15 匿名認証

10.15.1 概要

一般に、許可するものと明示的に禁止するものを明示的に指定する「デフォルトで拒否」を採用することは、優れたセキュリティ慣行と考えられています。特に Web アプリケーションの場合、認証されていないユーザーがアクセスできるものを定義することも同様の状況です。多くのサイトでは、いくつかの URL 以外(ホームページやログインページなど)についてユーザーを認証する必要があります。この場合、すべての保護されたリソースに対してではなく、これらの特定の URL に対してアクセス構成属性を定義するのが最も簡単です。別の言い方をすると、ROLE_SOMETHING がデフォルトで必要であり、アプリケーションのログイン、ログアウト、ホームページなど、このルールの特定の例外のみを許可すると言うのはいいことです。これらのページをフィルターチェーンから完全に省略して、アクセス制御チェックをバイパスすることもできますが、他の理由、特に認証されたユーザーに対してページの動作が異なる場合、これは望ましくない場合があります。

これが匿名認証の意味です。「匿名で認証された」ユーザーと認証されていないユーザーの間に実際の概念上の違いはないことに注意してください。Spring Security の匿名認証は、アクセス制御属性を構成するより便利な方法を提供するだけです。たとえば、getCallerPrincipal などのサーブレット API 呼び出しの呼び出しは、実際には SecurityContextHolder に匿名認証オブジェクトが存在する場合でも null を返します。

監査インターセプターが SecurityContextHolder を照会して、特定の操作を担当したプリンシパルを識別する場合など、匿名認証が役立つ状況が他にもあります。SecurityContextHolder が常に Authentication オブジェクトを含み、決して null を含まないことがわかっている場合、クラスをより堅牢に作成できます。

10.15.2 構成

匿名認証のサポートは、HTTP 構成 Spring Security 3.0 を使用すると自動的に提供され、<anonymous> 要素を使用してカスタマイズ(または無効化)できます。従来の Bean 構成を使用していない限り、ここで説明する Bean を構成する必要はありません。

匿名認証機能を一緒に提供する 3 つのクラス。AnonymousAuthenticationToken は Authentication の実装であり、匿名プリンシパルに適用される GrantedAuthority を格納します。AnonymousAuthenticationToken が受け入れられるように、ProviderManager にチェーンされた対応する AnonymousAuthenticationProvider があります。最後に、AnonymousAuthenticationFilter があります。これは、通常の認証メカニズムの後にチェーンされ、既存の Authentication が保持されていない場合、AnonymousAuthenticationToken を SecurityContextHolder に自動的に追加します。フィルターと認証プロバイダーの定義は次のように表示されます。

<bean id="anonymousAuthFilter"
    class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

<bean id="anonymousAuthenticationProvider"
    class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>

key はフィルターと認証プロバイダーの間で共有されるため、前者によって作成されたトークンは後者の [6] によって受け入れられます。userAttribute は usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority] の形式で表現されます。これは、InMemoryDaoImpl の userMap プロパティの等号の後に使用されるものと同じ構文です。

前に説明したように、匿名認証の利点は、すべての URI パターンにセキュリティを適用できることです。例:

<bean id="filterSecurityInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadata">
    <security:filter-security-metadata-source>
    <security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
    <security:intercept-url pattern='/**' access='ROLE_USER'/>
    </security:filter-security-metadata-source>" +
</property>
</bean>

10.15.3 AuthenticationTrustResolver

匿名認証の議論を締めくくるのは、AuthenticationTrustResolver インターフェースとそれに対応する AuthenticationTrustResolverImpl 実装です。このインターフェースは isAnonymous(Authentication) メソッドを提供します。これにより、関心のあるクラスはこの特別なタイプの認証ステータスを考慮することができます。ExceptionTranslationFilter は、AccessDeniedException の処理にこのインターフェースを使用します。AccessDeniedException がスローされ、認証が匿名タイプの場合、403(禁止)レスポンスをスローする代わりに、フィルターは代わりに AuthenticationEntryPoint を開始し、プリンシパルが正しく認証できるようにします。これは必要な区別です。そうでなければ、プリンシパルは常に「認証済み」とみなされ、フォーム、基本、ダイジェスト、またはその他の通常の認証メカニズムを介してログインする機会が与えられません。

多くの場合、上記のインターセプター構成の ROLE_ANONYMOUS 属性が IS_AUTHENTICATED_ANONYMOUSLY に置き換えられます。これは、アクセス制御を定義するときに事実上同じものです。これは認可章で見る AuthenticatedVoter の使用例です。AuthenticationTrustResolver を使用して、この特定の構成属性を処理し、匿名ユーザーにアクセスを認可します。AuthenticatedVoter アプローチは、匿名ユーザー、remember-me ユーザー、および完全に認証されたユーザーを区別できるため、より強力です。ただし、この機能が必要ない場合は、ROLE_ANONYMOUS を使用できます。ROLE_ANONYMOUS は、Spring Security の標準 RoleVoter によって処理されます。

10.16 事前認証シナリオ

Spring Security を認可に使用したい場合もありますが、ユーザーはアプリケーションにアクセスする前に外部システムによってすでに確実に認証されています。これらの状況を「事前認証済み」シナリオと呼びます。例には、X.509、Siteminder、アプリケーションが実行されている Java EE コンテナーによる認証が含まれます。事前認証を使用する場合、Spring Security は

  • リクエストを行っているユーザーを特定します。
  • ユーザーの権限を取得します。

詳細は、外部認証メカニズムによって異なります。ユーザーは、X.509 の場合は証明書情報によって、Siteminder の場合は HTTP リクエストヘッダーによって識別されます。コンテナー認証に依存している場合、ユーザーは受信 HTTP リクエストで getUserPrincipal() メソッドを呼び出すことにより識別されます。場合によっては、外部メカニズムがユーザーにロール / 権限情報を提供することがありますが、別の場合は、UserDetailsService などの別のソースから権限を取得する必要があります。

10.16.1 事前認証フレームワーククラス

ほとんどの事前認証メカニズムは同じパターンに従うため、Spring Security には事前認証された認証プロバイダーを実装するための内部フレームワークを提供するクラスのセットがあります。これにより、重複がなくなり、新しい実装を構造化された方法で追加できるようになり、すべてをゼロから記述する必要がなくなります。X.509 認証のようなものを使用する場合、これらのクラスについて知る必要はありません。すでに使用しやすく、使いやすい名前空間設定オプションがすでにあるためです。明示的な Bean 構成を使用する必要がある場合、または独自の実装の作成を計画している場合は、提供された実装がどのように機能するかを理解しておくと役立ちます。org.springframework.security.web.authentication.preauth にクラスがあります。ここで概要を説明するだけなので、必要に応じて Javadoc とソースを参照してください。

AbstractPreAuthenticatedProcessingFilter

このクラスは、セキュリティコンテキストの現在の内容をチェックし、空の場合、HTTP リクエストからユーザー情報を抽出して AuthenticationManager に送信しようとします。この情報を取得するために、サブクラスは次のメソッドをオーバーライドします。

protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);

protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);

これらを呼び出した後、フィルターは返されたデータを含む PreAuthenticatedAuthenticationToken を作成し、認証のために送信します。ここでの「認証」とは、ユーザーの権限をロードするためのさらなる処理を意味しますが、標準の Spring Security 認証アーキテクチャに従います。

他の Spring Security 認証フィルターと同様に、事前認証フィルターには authenticationDetailsSource プロパティがあり、デフォルトで WebAuthenticationDetails オブジェクトを作成して、Authentication オブジェクトの details プロパティにセッション ID や発信元 IP アドレスなどの追加情報を保存します。事前認証メカニズムからユーザーロール情報を取得できる場合、データもこのプロパティに格納され、詳細が GrantedAuthoritiesContainer インターフェースを実装します。これにより、認証プロバイダーは、ユーザーに外部的に割り当てられた権限を読み取ることができます。次に具体的な例を見てみましょう。

J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource

このクラスのインスタンスである authenticationDetailsSource でフィルターが構成されている場合、権限情報は、「マップ可能なロール」の所定のセットごとに isUserInRole(String role) メソッドを呼び出すことによって取得されます。クラスは、構成された MappableAttributesRetriever からこれらを取得します。可能な実装には、アプリケーションコンテキストでリストをハードコードし、web.xml ファイルの <security-role> 情報からロール情報を読み取ることが含まれます。事前認証サンプルアプリケーションは、後者のアプローチを使用します。

構成された Attributes2GrantedAuthoritiesMapper を使用して、ロール(または属性)が Spring Security GrantedAuthority オブジェクトにマップされる追加の段階があります。デフォルトでは、通常の ROLE_ プレフィックスが名前に追加されますが、動作を完全に制御できます。

PreAuthenticatedAuthenticationProvider

事前認証されたプロバイダーは、ユーザーのために UserDetails オブジェクトをロードする以上のことはほとんどありません。AuthenticationUserDetailsService に委譲することでこれを行います。後者は標準の UserDetailsService に似ていますが、単なるユーザー名ではなく Authentication オブジェクトを取ります。

public interface AuthenticationUserDetailsService {
    UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
}

このインターフェースには他の用途もありますが、事前認証を使用すると、前のセクションで見たように、Authentication オブジェクトにパッケージ化された機関にアクセスできます。PreAuthenticatedGrantedAuthoritiesUserDetailsService クラスがこれを行います。または、UserDetailsByNameServiceWrapper 実装を介して標準 UserDetailsService に委譲することもできます。

Http403ForbiddenEntryPoint

AuthenticationEntryPoint については、技術概要の章で説明しました。通常、認証されていないユーザー(保護されたリソースにアクセスしようとした場合)の認証プロセスを開始するロールを果たしますが、事前認証された場合は適用されません。事前認証を他の認証メカニズムと組み合わせて使用していない場合にのみ、このクラスのインスタンスで ExceptionTranslationFilter を構成します。ユーザーが AbstractPreAuthenticatedProcessingFilter によって拒否され、認証が null になった場合に呼び出されます。呼び出されると、常に 403 -forbidden レスポンスコードを返します。

10.16.2 具体的な実装

X.509 認証はそれ自身の章でカバーされています。ここでは、事前に認証された他のシナリオをサポートするクラスをいくつか見ていきます。

リクエストヘッダー認証 (Siteminder)

外部認証システムは、HTTP リクエストに特定のヘッダーを設定することにより、アプリケーションに情報を提供できます。このよく知られた例は Siteminder で、SM_USER というヘッダーでユーザー名を渡します。このメカニズムは、RequestHeaderAuthenticationFilter クラスによってサポートされており、ヘッダーからユーザー名を抽出するだけです。デフォルトでは、名前 SM_USER がヘッダー名として使用されます。詳細については、Javadoc を参照してください。

[Tip] ヒント

このようなシステムを使用する場合、フレームワークは認証チェックをまったく実行しないため、外部システムが適切に構成され、アプリケーションへのすべてのアクセスを保護することが非常に重要です。攻撃者がこれを検出せずに元のリクエストのヘッダーを偽造できる場合、希望するユーザー名を選択する可能性があります。

Siteminder の構成例

このフィルターを使用した典型的な構成は次のようになります。

<security:http>
<!-- Additional http configuration omitted -->
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>

<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>

<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
    <bean id="userDetailsServiceWrapper"
        class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
    <property name="userDetailsService" ref="userDetailsService"/>
    </bean>
</property>
</bean>

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>

ここでは、構成にセキュリティ名前空間が使用されていると想定しています。また、ユーザーのロールをロードするために、構成に UserDetailsService (「userDetailsService」と呼ばれる)を追加したことを前提としています。

Java EE コンテナー認証

クラス J2eePreAuthenticatedProcessingFilter は、HttpServletRequest の userPrincipal プロパティからユーザー名を抽出します。このフィルターの使用は、通常、「J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource」と呼ばれるセクションで説明したように、 Java EE ロールの使用と組み合わされます。

このアプローチを使用するコードベースにはサンプルアプリケーションがあります。そのため、github からコードを入手し、興味がある場合はアプリケーションコンテキストファイルを確認してください。コードは samples/xml/preauth ディレクトリにあります。

10.17 Java 認証および認可サービス(JAAS)プロバイダー

10.17.1 概要

Spring Security は、認証リクエストを Java Authentication and Authorization Service(JAAS)に委譲できるパッケージを提供します。このパッケージについては、以下で詳しく説明します。

10.17.2 AbstractJaasAuthenticationProvider

AbstractJaasAuthenticationProvider は、提供されている JAAS AuthenticationProvider 実装の基礎です。サブクラスは、LoginContext を作成するメソッドを実装する必要があります。AbstractJaasAuthenticationProvider には、以下で説明する多くの依存関係があります。

JAAS CallbackHandler

ほとんどの JAAS LoginModule には、何らかのコールバックが必要です。これらのコールバックは通常、ユーザーからユーザー名とパスワードを取得するために使用されます。

Spring Security デプロイでは、Spring Security が(認証メカニズムを介して)このユーザー対話を担当します。認証リクエストが JAAS に委譲されるまでに、Spring Security の認証メカニズムは、JAAS LoginModule が必要とするすべての情報を含む Authentication オブジェクトをすでに完全に取り込みました。

Spring Security の JAAS パッケージは、2 つのデフォルトコールバックハンドラー JaasNameCallbackHandler と JaasPasswordCallbackHandler を提供します。これらの各コールバックハンドラーは JaasAuthenticationCallbackHandler を実装します。ほとんどの場合、これらのコールバックハンドラーは、内部の仕組みを理解しなくても簡単に使用できます。

コールバックの動作を完全に制御する必要がある場合、内部で AbstractJaasAuthenticationProvider はこれらの JaasAuthenticationCallbackHandler を InternalCallbackHandler でラップします。InternalCallbackHandler は、JAAS の通常の CallbackHandler インターフェースを実際に実装するクラスです。JAAS LoginModule が使用されるたびに、InternalCallbackHandler で構成されたアプリケーションコンテキストのリストが渡されます。LoginModule が InternalCallbackHandler に対してコールバックをリクエストする場合、コールバックはラップされている JaasAuthenticationCallbackHandler に順番に渡されます。

JAAS AuthorityGranter

JAAS はプリンシパルと連携します。「ロール」でさえ、JAAS のプリンシパルとして表されます。一方、Spring Security は Authentication オブジェクトで機能します。各 Authentication オブジェクトには、単一のプリンシパルと複数の GrantedAuthority が含まれます。これらの異なる概念間のマッピングを容易にするために、Spring Security の JAAS パッケージには AuthorityGranter インターフェースが含まれています。

AuthorityGranter は、JAAS プリンシパルをインスペクションし、プリンシパルに割り当てられた権限を表す String のセットを返す責任があります。返された権限文字列ごとに、AbstractJaasAuthenticationProvider は、権限文字列と AuthorityGranter が渡された JAAS プリンシパルを含む JaasGrantedAuthority (Spring Security の GrantedAuthority インターフェースを実装)を作成します。AbstractJaasAuthenticationProvider は、最初に JAAS LoginModule を使用してユーザーの資格情報を正常に認証し、それが返す LoginContext にアクセスすることにより、JAAS プリンシパルを取得します。LoginContext.getSubject().getPrincipals() の呼び出しが行われ、結果の各プリンシパルが AbstractJaasAuthenticationProvider.setAuthorityGranters(List) プロパティに対して定義された各 AuthorityGranter に渡されます。

すべての JAAS プリンシパルには実装固有の意味があるため、Spring Security には本番 AuthorityGranter は含まれません。ただし、単体テストには、簡単な AuthorityGranter 実装を示す TestAuthorityGranter があります。

10.17.3 DefaultJaasAuthenticationProvider

DefaultJaasAuthenticationProvider では、JAAS Configuration オブジェクトを依存関係として挿入できます。次に、注入された JAAS Configuration を使用して LoginContext を作成します。これは、DefaultJaasAuthenticationProvider が JaasAuthenticationProvider のように Configuration の特定の実装にバインドされていないことを意味します。

InMemoryConfiguration

Configuration を DefaultJaasAuthenticationProvider に挿入しやすくするために、InMemoryConfiguration という名前のデフォルトのメモリ内実装が提供されています。実装コンストラクターは、各キーがログイン構成名を表し、値が AppConfigurationEntry の Array を表す Map を受け入れます。InMemoryConfiguration は、提供された Map 内でマッピングが見つからない場合に使用される AppConfigurationEntry オブジェクトのデフォルト Array もサポートします。詳細については、InMemoryConfiguration のクラスレベルの javadoc を参照してください。

DefaultJaasAuthenticationProvider の構成例

InMemoryConfiguration の Spring 構成は標準の JAAS 構成ファイルよりも詳細になりますが、DefaultJaasAuthenticationProvider と組み合わせて使用すると、デフォルトの Configuration 実装に依存しないため、JaasAuthenticationProvider よりも柔軟です。

InMemoryConfiguration を使用した DefaultJaasAuthenticationProvider の構成例を以下に示します。Configuration のカスタム実装は、DefaultJaasAuthenticationProvider にも簡単に挿入できることに注意してください。

<bean id="jaasAuthProvider"
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
<property name="configuration">
<bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
<constructor-arg>
    <map>
    <!--
    SPRINGSECURITY is the default loginContextName
    for AbstractJaasAuthenticationProvider
    -->
    <entry key="SPRINGSECURITY">
    <array>
    <bean class="javax.security.auth.login.AppConfigurationEntry">
        <constructor-arg value="sample.SampleLoginModule" />
        <constructor-arg>
        <util:constant static-field=
            "javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/>
        </constructor-arg>
        <constructor-arg>
        <map></map>
        </constructor-arg>
        </bean>
    </array>
    </entry>
    </map>
    </constructor-arg>
</bean>
</property>
<property name="authorityGranters">
<list>
    <!-- You will need to write your own implementation of AuthorityGranter -->
    <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
</list>
</property>
</bean>

10.17.4 JaasAuthenticationProvider

JaasAuthenticationProvider は、デフォルトの Configuration が ConfigFile: Oracle (英語) のインスタンスであると想定しています。この仮定は、Configuration を更新しようとするために行われます。JaasAuthenticationProvider は、デフォルトの Configuration を使用して LoginContext を作成します。

JAAS ログイン構成ファイル /WEB-INF/login.conf があり、次の内容があるとします。

JAASTest {
    sample.SampleLoginModule required;
};

すべての Spring Security Bean と同様に、JaasAuthenticationProvider はアプリケーションコンテキストを介して設定されます。次の定義は、上記の JAAS ログイン構成ファイルに対応します。

<bean id="jaasAuthenticationProvider"
class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
<property name="loginConfig" value="/WEB-INF/login.conf"/>
<property name="loginContextName" value="JAASTest"/>
<property name="callbackHandlers">
<list>
<bean
    class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>
<bean
    class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>
</list>
</property>
<property name="authorityGranters">
    <list>
    <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
    </list>
</property>
</bean>

10.17.5 サブジェクトとして実行

構成されている場合、JaasApiIntegrationFilter は JaasAuthenticationToken で Subject として実行しようとします。これは、以下を使用して Subject にアクセスできることを意味します。

Subject subject = Subject.getSubject(AccessController.getContext());

この統合は、jaas-api-provision 属性を使用して簡単に構成できます。この機能は、実装されている JAAS サブジェクトに依存するレガシーまたは外部 API と統合する場合に役立ちます。

10.18 CAS 認証

10.18.1 概要

JA-SIG は、CAS として知られるエンタープライズ規模のシングルサインオンシステムを作成します。他のイニシアチブとは異なり、JA-SIG の中央認証サービスはオープンソースであり、広く使用され、理解しやすく、プラットフォームに依存せず、プロキシ機能をサポートしています。Spring Security は CAS を完全にサポートし、Spring Security の単一アプリケーションデプロイから企業全体の CAS サーバーで保護された複数アプリケーションデプロイへの簡単な移行パスを提供します。

CAS の詳細については、https://www.apereo.org (英語) を参照してください。CAS サーバーファイルをダウンロードするには、このサイトにアクセスする必要もあります。

10.18.2 CAS の仕組み

CAS の Web サイトには CAS のアーキテクチャを詳しく説明するドキュメントが含まれていますが、ここでは Spring Security のコンテキスト内で一般的な概要を再度示します。Spring Security 3.x は CAS 3 をサポートしています。執筆時点では、CAS サーバーのバージョンは 3.4 でした。

企業のどこかで CAS サーバーをセットアップする必要があります。CAS サーバーは単なる標準の WAR ファイルであるため、サーバーをセットアップするのは難しくありません。WAR ファイル内では、ユーザーに表示されるログインおよびその他のシングルサインオンページをカスタマイズします。

CAS 3.4 サーバーをデプロイする場合、CAS に含まれる deployerConfigContext.xml で AuthenticationHandler を指定する必要もあります。AuthenticationHandler には、指定された資格情報のセットが有効かどうかについてブール値を返す単純なメソッドがあります。AuthenticationHandler 実装は、LDAP サーバーやデータベースなど、何らかのタイプのバックエンド認証リポジトリにリンクする必要があります。CAS 自体には、これを支援する多数の AuthenticationHandler がすぐに使用できます。サーバー war ファイルをダウンロードしてデプロイすると、ユーザー名と一致するパスワードを入力したユーザーを正常に認証するようにセットアップされます。これはテストに役立ちます。

CAS サーバー自体とは別に、他の主要なプレーヤーはもちろん、企業全体にデプロイされている安全な Web アプリケーションです。これらの Web アプリケーションは「サービス」と呼ばれます。サービスには 3 つのタイプがあります。サービスチケットを認証するもの、プロキシチケットを取得できるもの、プロキシチケットを認証するもの。プロキシチケットの認証は、プロキシのリストを検証する必要があり、多くの場合、プロキシチケットを再利用できるため、異なります。

Spring Security と CAS 相互作用シーケンス

Web ブラウザー、CAS サーバー、Spring セキュリティで保護されたサービス間の基本的な相互作用は次のとおりです。

  • Web ユーザーはサービスの公開ページを閲覧しています。CAS または Spring Security は関係しません。
  • ユーザーは最終的に、安全なページ、または使用する Bean の 1 つが安全なページをリクエストします。Spring Security の ExceptionTranslationFilter は AccessDeniedException または AuthenticationException を検出します。
  • ユーザーの Authentication オブジェクト(またはその欠如)が AuthenticationException を引き起こしたため、ExceptionTranslationFilter は設定された AuthenticationEntryPoint を呼び出します。CAS を使用する場合、これは CasAuthenticationEntryPoint クラスになります。
  • CasAuthenticationEntryPoint は、ユーザーのブラウザーを CAS サーバーにリダイレクトします。また、Spring Security サービス (あなたのアプリケーション) のコールバック URL である service パラメーターも示します。例: ブラウザーのリダイレクト先の URL は https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas (英語) の可能性があります。
  • ユーザーのブラウザーが CAS にリダイレクトすると、ユーザー名とパスワードの入力を求められます。ユーザーが以前にログオンしたことを示すセッション Cookie を提示した場合、再度ログインするように求められることはありません(この手順には例外があります。これについては後で説明します)。CAS は、上記の PasswordHandler (または CAS 3.0 を使用する場合は AuthenticationHandler)を使用して、ユーザー名とパスワードが有効かどうかを判断します。
  • ログインに成功すると、CAS はユーザーのブラウザーを元のサービスにリダイレクトします。また、ticket パラメーターも含まれます。これは、「サービスチケット」を表す不透明な文字列です。前の例を続けると、ブラウザーがリダイレクトされる URL は https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ (英語) になる可能性があります。
  • サービス Web アプリケーションに戻ると、CasAuthenticationFilter は常に /login/cas へのリクエストをリッスンしています(これは構成可能ですが、この導入ではデフォルトを使用します)。処理フィルターは、サービスチケットを表す UsernamePasswordAuthenticationToken を構築します。プリンシパルは CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER に等しくなりますが、クレデンシャルはサービスチケットの不透明な値になります。この認証リクエストは、構成された AuthenticationManager に渡されます。
  • AuthenticationManager 実装は ProviderManager になり、ProviderManager は CasAuthenticationProvider で構成されます。CasAuthenticationProvider は、CAS 固有のプリンシパル(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER など)および CasAuthenticationToken (後述)を含む UsernamePasswordAuthenticationToken にのみ応答します。
  • CasAuthenticationProvider は、TicketValidator 実装を使用してサービスチケットを検証します。これは通常、CAS クライアントライブラリに含まれるクラスの 1 つである Cas20ServiceTicketValidator です。アプリケーションがプロキシチケットを検証する必要がある場合、Cas20ProxyTicketValidator が使用されます。TicketValidator は、サービスチケットを検証するために CAS サーバーに HTTPS リクエストを行います。この例に含まれているプロキシコールバック URL https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptor (英語) も含まれる場合があります。
  • CAS サーバーに戻ると、検証リクエストが受信されます。提示されたサービスチケットが、チケットが発行されたサービス URL と一致する場合、CAS はユーザー名を示す肯定レスポンスを XML で提供します。認証にプロキシが関与している場合(後述)、プロキシのリストも XML レスポンスに含まれます。
  • [ オプション ] CAS 検証サービスへのリクエストにプロキシコールバック URL(pgtUrl パラメーター内)が含まれていた場合、CAS は XML レスポンスに pgtIou 文字列を含めます。この pgtIou は、プロキシ許可チケット IOU を表します。CAS サーバーは、pgtUrl への独自の HTTPS 接続を作成します。これは、CAS サーバーとリクエストされたサービス URL を相互に認証するためです。HTTPS 接続を使用して、プロキシ許可チケットを元の Web アプリケーションに送信します。例: https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH (英語)
  • Cas20TicketValidator は、CAS サーバーから受信した XML を解析します。CasAuthenticationProvider に TicketResponse を返します。これには、ユーザー名(必須)、プロキシリスト(含まれている場合)、およびプロキシ許可チケット IOU(プロキシコールバックがリクエストされた場合)が含まれます。
  • 次に、CasAuthenticationProvider は構成済みの CasProxyDecider を呼び出します。CasProxyDecider は、TicketResponse のプロキシリストがサービスに受け入れられるかどうかを示します。Spring Security にはいくつかの実装が用意されています: RejectProxyTicketsAcceptAnyCasProxyNamedCasProxyDecider。これらの名前は、信頼できるプロキシの List を提供できる NamedCasProxyDecider を除いて、ほとんど自明です。
  • CasAuthenticationProvider は、次に Assertion に含まれるユーザーに適用される GrantedAuthority オブジェクトをロードするために AuthenticationUserDetailsService をリクエストします。
  • 問題がなければ、CasAuthenticationProvider は TicketResponse および GrantedAuthority に含まれる詳細を含む CasAuthenticationToken を作成します。
  • 次に、制御が CasAuthenticationFilter に戻り、作成された CasAuthenticationToken がセキュリティコンテキストに配置されます。
  • ユーザーのブラウザーは、AuthenticationException (または構成に応じてカスタム宛先)を引き起こした元のページにリダイレクトされます。

まだここにいるのは良いことです! これがどのように構成されているか見てみましょう

10.18.3 CAS クライアントの構成

CAS の Web アプリケーション側は、Spring Security により簡単になりました。Spring Security の使用の基本をすでに知っていることを前提としているため、これらについては以下で再度説明しません。名前空間ベースの構成が使用されていると想定し、必要に応じて CAS Bean を追加します。各セクションは前のセクションに基づいています。完全な CAS サンプルアプリケーションは Spring Security サンプルにあります。

サービスチケット認証

このセクションでは、Spring Security をセットアップしてサービスチケットを認証する方法について説明します。多くの場合、これはすべて Web アプリケーションに必要です。ServiceProperties Bean をアプリケーションコンテキストに追加する必要があります。これは CAS サービスを表しています:

<bean id="serviceProperties"
    class="org.springframework.security.cas.ServiceProperties">
<property name="service"
    value="https://localhost:8443/cas-sample/login/cas"/>
<property name="sendRenew" value="false"/>
</bean>

service は、CasAuthenticationFilter によって監視される URL と等しくなければなりません。sendRenew のデフォルトは false ですが、アプリケーションが特に敏感な場合は true に設定する必要があります。このパラメーターは、シングルサインオンログインが受け入れられないことを CAS ログインサービスに通知します。代わりに、ユーザーはサービスにアクセスするためにユーザー名とパスワードを再入力する必要があります。

CAS 認証プロセスを開始するには、次の Bean を構成する必要があります(ネームスペース構成を使用していると仮定)。

<security:http entry-point-ref="casEntryPoint">
...
<security:custom-filter position="CAS_FILTER" ref="casFilter" />
</security:http>

<bean id="casFilter"
    class="org.springframework.security.cas.web.CasAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>

<bean id="casEntryPoint"
    class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<property name="loginUrl" value="https://localhost:9443/cas/login"/>
<property name="serviceProperties" ref="serviceProperties"/>
</bean>

CAS が動作するには、ExceptionTranslationFilter の authenticationEntryPoint プロパティが CasAuthenticationEntryPoint Bean に設定されている必要があります。これは、上の例のように entry-point-ref を使用して簡単に実行できます。CasAuthenticationEntryPoint は、企業の CAS ログインサーバーへの URL を提供する ServiceProperties Bean(前述)を参照する必要があります。これは、ユーザーのブラウザーがリダイレクトされる場所です。

CasAuthenticationFilter には、UsernamePasswordAuthenticationFilter (フォームベースのログインに使用)と非常によく似たプロパティがあります。これらのプロパティを使用して、認証の成功と失敗の動作などをカスタマイズできます。

次に、CasAuthenticationProvider とその協力者を追加する必要があります。

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="casAuthenticationProvider" />
</security:authentication-manager>

<bean id="casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
    <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
    <constructor-arg ref="userService" />
    </bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
    <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
    <constructor-arg index="0" value="https://localhost:9443/cas" />
    </bean>
</property>
<property name="key" value="an_id_for_this_auth_provider_only"/>
</bean>

<security:user-service id="userService">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used.
This is not safe for production, but makes reading
in samples easier.
Normally passwords should be hashed using BCrypt -->
<security:user name="joe" password="{noop}joe" authorities="ROLE_USER" />
...
</security:user-service>

CasAuthenticationProvider は、CAS によって認証されると、UserDetailsService インスタンスを使用してユーザーの権限を読み込みます。ここでは、簡単なメモリ内セットアップを示しました。CasAuthenticationProvider は実際には認証にパスワードを使用しないが、権限を使用することに注意してください。

CAS の仕組みセクションに戻って参照すると、Bean はすべて合理的に自明です。

これで、CAS の最も基本的な構成が完了しました。間違いを犯していない場合、Web アプリケーションは CAS シングルサインオンのフレームワーク内で問題なく動作するはずです。Spring Security の他の部分は、CAS が認証を処理したという事実を心配する必要はありません。次のセクションでは、いくつかの(オプションの)より高度な構成について説明します。

シングルログアウト

CAS プロトコルはシングルログアウトをサポートし、Spring Security 構成に簡単に追加できます。以下は、シングルログアウトを処理する Spring Security 構成の更新です。

<security:http entry-point-ref="casEntryPoint">
...
<security:logout logout-success-url="/cas-logout.jsp"/>
<security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
<security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
</security:http>

<!-- This filter handles a Single Logout Request from the CAS Server -->
<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>

<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
<bean id="requestSingleLogoutFilter"
    class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="https://localhost:9443/cas/logout"/>
<constructor-arg>
    <bean class=
        "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout/cas"/>
</bean>

logout 要素はユーザーをローカルアプリケーションからログアウトしますが、CAS サーバーまたはログインしている他のアプリケーションとのセッションを終了しません。requestSingleLogoutFilter フィルターにより、/spring_security_cas_logout の URL をリクエストして、構成済みの CAS サーバーのログアウト URL にアプリケーションをリダイレクトすることができます。次に、CAS サーバーは、サインインしたすべてのサービスにシングルログアウトリクエストを送信します。singleLogoutFilter は、静的 Map で HttpSession を検索して無効化することにより、シングルログアウトリクエストを処理します。

logout 要素と singleLogoutFilter の両方が必要な理由は混乱するかもしれません。SingleSignOutFilter は HttpSession を静的 Map に保存するだけで、その上で無効化を呼び出すため、最初にローカルでログアウトすることをお勧めします。上記の構成では、ログアウトのフローは次のようになります。

  • ユーザーは /logout をリクエストします。/logout はユーザーをローカルアプリケーションからログアウトし、ログアウト成功ページに送信します。
  • ログアウト成功ページ /cas-logout.jsp は、すべてのアプリケーションからログアウトするために、/logout/cas を指すリンクをクリックするようユーザーに指示する必要があります。
  • ユーザーがリンクをクリックすると、ユーザーは CAS シングルログアウト URL(https://localhost:9443/cas/logout)にリダイレクトされます。
  • CAS サーバー側では、CAS シングルログアウト URL がシングルログアウトリクエストをすべての CAS サービスに送信します。CAS サービス側では、JASIG の SingleSignOutFilter は元のセッションを無効化することによりログアウトリクエストを処理します。

次のステップは、web.xml に以下を追加することです

<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>
    org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
    org.jasig.cas.client.session.SingleSignOutHttpSessionListener
</listener-class>
</listener>

SingleSignOutFilter を使用すると、エンコードの問題が発生する場合があります。CharacterEncodingFilter を追加して、SingleSignOutFilter を使用するときに文字エンコードが正しいことを確認することをお勧めします。繰り返しになりますが、詳細については JASIG のドキュメントを参照してください。SingleSignOutHttpSessionListener は、HttpSession の有効期限が切れると、シングルログアウトに使用されたマッピングが削除されるようにします。

CAS を使用したステートレスサービスへの認証

このセクションでは、CAS を使用してサービスに対して認証する方法について説明します。つまり、このセクションでは、CAS で認証するサービスを使用するクライアントをセットアップする方法について説明します。次のセクションでは、CAS を使用して認証するステートレスサービスをセットアップする方法について説明します。

プロキシ許可チケットを取得するための CAS の構成

ステートレスサービスを認証するには、アプリケーションはプロキシ許可チケット(PGT)を取得する必要があります。このセクションでは、thencas-st [Service Ticket Authentication] 設定時に PGT を取得するために Spring Security を設定する方法について説明します。

最初のステップは、Spring Security 構成に ProxyGrantingTicketStorage を含めることです。これは、プロキシチケットを取得するために使用できるように、CasAuthenticationFilter によって取得された PGT を保存するために使用されます。構成例を以下に示します

<!--
NOTE: In a real application you should not use an in memory implementation.
You will also want to ensure to clean up expired tickets by calling
ProxyGrantingTicketStorage.cleanup()
-->
<bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>

次のステップは、CasAuthenticationProvider を更新してプロキシチケットを取得できるようにすることです。これを行うには、Cas20ServiceTicketValidator を Cas20ProxyTicketValidator に置き換えます。proxyCallbackUrl は、アプリケーションが PGT を受け取る URL に設定する必要があります。最後に、構成は PGT を使用してプロキシチケットを取得できるように、ProxyGrantingTicketStorage も参照する必要があります。以下に行う必要がある構成変更の例を見つけることができます。

<bean id="casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
    <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
    <constructor-arg value="https://localhost:9443/cas"/>
        <property name="proxyCallbackUrl"
        value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/>
    <property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
    </bean>
</property>
</bean>

最後のステップは、CasAuthenticationFilter を更新して PGT を受け入れ、ProxyGrantingTicketStorage に保管することです。proxyReceptorUrl が Cas20ProxyTicketValidator の proxyCallbackUrl と一致することが重要です。構成例を以下に示します。

<bean id="casFilter"
        class="org.springframework.security.cas.web.CasAuthenticationFilter">
    ...
    <property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
    <property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/>
</bean>
プロキシチケットを使用したステートレスサービスの呼び出し

Spring Security が PGT を取得したため、使用して、ステートレスサービスの認証に使用できるプロキシチケットを作成できます。CAS サンプルアプリケーションには、ProxyTicketSampleServlet の実例が含まれています。以下にサンプルコードを示します。

protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);

// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
...
}

代理チケット認証

CasAuthenticationProvider は、ステートフルクライアントとステートレスクライアントを区別します。ステートフルクライアントは、CasAuthenticationFilter の filterProcessUrl に送信するものと見なされます。ステートレスクライアントは、filterProcessUrl 以外の URL で CasAuthenticationFilter に認証リクエストを提示するものです。

リモーティングプロトコルには HttpSession のコンテキスト内で自身を提示する方法がないため、リクエスト間のセッションにセキュリティコンテキストを保存するデフォルトのプラクティスに依存することはできません。さらに、TicketValidator によって検証された後、CAS サーバーはチケットを無効にするため、後続のリクエストで同じプロキシチケットを提示しても機能しません。

明らかなオプションの 1 つは、リモートプロトコルクライアントに CAS をまったく使用しないことです。ただし、これは CAS の多くの望ましい機能を削除します。中間として、CasAuthenticationProvider は StatelessTicketCache を使用します。これは、CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER に等しいプリンシパルを使用するステートレスクライアントにのみ使用されます。CasAuthenticationProvider は、結果の CasAuthenticationToken を StatelessTicketCache に保存し、プロキシチケットにキーを設定します。リモーティングプロトコルクライアントは同じプロキシチケットを提示することができ、CasAuthenticationProvider は検証のために CAS サーバーに接続する必要はありません(最初のリクエストを除く)。認証されると、プロキシチケットは元のターゲットサービス以外の URL に使用できます。

このセクションは、プロキシチケット認証に対応するために、前のセクションに基づいています。最初のステップは、以下に示すように、すべてのアーティファクトの認証を指定することです。

<bean id="serviceProperties"
    class="org.springframework.security.cas.ServiceProperties">
...
<property name="authenticateAllArtifacts" value="true"/>
</bean>

次のステップは、serviceProperties と CasAuthenticationFilter の authenticationDetailsSource を指定することです。serviceProperties プロパティは、filterProcessUrl に存在するものだけではなく、すべてのアーティファクトを認証しようとするように CasAuthenticationFilter に指示します。ServiceAuthenticationDetailsSource は ServiceAuthenticationDetails を作成し、HttpServletRequest に基づいて現在の URL がチケットの検証時にサービス URL として使用されるようにします。サービス URL を生成する方法は、カスタム ServiceAuthenticationDetails を返すカスタム AuthenticationDetailsSource を注入することによりカスタマイズできます。

<bean id="casFilter"
    class="org.springframework.security.cas.web.CasAuthenticationFilter">
...
<property name="serviceProperties" ref="serviceProperties"/>
<property name="authenticationDetailsSource">
    <bean class=
    "org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
    <constructor-arg ref="serviceProperties"/>
    </bean>
</property>
</bean>

プロキシチケットを処理するには、CasAuthenticationProvider を更新する必要もあります。これを行うには、Cas20ServiceTicketValidator を Cas20ProxyTicketValidator に置き換えます。statelessTicketCache と、受け入れたいプロキシを設定する必要があります。すべてのプロキシを受け入れるために必要な更新の例を以下に示します。

<bean id="casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
    <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
    <constructor-arg value="https://localhost:9443/cas"/>
    <property name="acceptAnyProxy" value="true"/>
    </bean>
</property>
<property name="statelessTicketCache">
    <bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
    <property name="cache">
        <bean class="net.sf.ehcache.Cache"
            init-method="initialise" destroy-method="dispose">
        <constructor-arg value="casTickets"/>
        <constructor-arg value="50"/>
        <constructor-arg value="true"/>
        <constructor-arg value="false"/>
        <constructor-arg value="3600"/>
        <constructor-arg value="900"/>
        </bean>
    </property>
    </bean>
</property>
</bean>

10.19 X.509 認証

10.19.1 概要

X.509 証明書認証の最も一般的な用途は、SSL を使用する場合、最も一般的にはブラウザーから HTTPS を使用する場合のサーバーの ID の検証です。ブラウザーは、サーバーによって提示された証明書が、管理している信頼できる認証局のリストの 1 つによって発行された(つまり、デジタル署名されている)ことを自動的に確認します。

「相互認証」で SSL を使用することもできます。サーバーは、SSL ハンドシェイクの一部としてクライアントに有効な証明書をリクエストします。サーバーは、証明書が受け入れ可能な機関によって署名されていることを確認することにより、クライアントを認証します。有効な証明書が提供されている場合、アプリケーションのサーブレット API を介して取得できます。Spring Security X.509 モジュールは、フィルターを使用して証明書を抽出します。証明書をアプリケーションユーザーにマップし、そのユーザーの付与された権限のセットをロードして、標準 Spring Security インフラストラクチャで使用します。

Spring Security で証明書を使用する前に、証明書の使用とサーブレットコンテナーのクライアント認証の設定に精通している必要があります。ほとんどの作業は、適切な証明書とキーの作成とインストールです。例: Tomcat を使用している場合は、こちらの手順 https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html (英語) を参照してください。Spring Security で試す前に、これを機能させることが重要です

10.19.2 Web アプリケーションへの X.509 認証の追加

X.509 クライアント認証の有効化は非常に簡単です。<x509/> 要素を http セキュリティ名前空間の構成に追加するだけです。

<http>
...
    <x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
</http>

要素には 2 つのオプション属性があります。

  • subject-principal-regex。証明書のサブジェクト名からユーザー名を抽出するために使用される正規表現。デフォルト値は上に示されています。これは、ユーザーの権限をロードするために UserDetailsService に渡されるユーザー名です。
  • user-service-ref。これは、X.509 で使用される UserDetailsService の Bean ID です。アプリケーションコンテキストで定義されているのが 1 つだけの場合は必要ありません。

subject-principal-regex には単一のグループが含まれている必要があります。たとえば、デフォルトの表現「CN =(.* ? )」は、共通名フィールドと一致します。そのため、証明書のサブジェクト名が「CN = Jimi Hendrix、OU = …」である場合、これは「Jimi Hendrix」のユーザー名を与えます。一致は大文字と小文字を区別しません。「emailAddress =(.* ? )、」は「EMAILADDRESS = [ メール保護 ] (英語) 、CN = …」と一致し、ユーザー名「[ メール保護 ] (英語) 」を与えます。クライアントが証明書を提示し、有効なユーザー名が正常に抽出された場合、セキュリティコンテキストに有効な Authentication オブジェクトがあるはずです。証明書が見つからない場合、または対応するユーザーが見つからない場合、セキュリティコンテキストは空のままになります。これは、フォームベースのログインなどの他のオプションで X.509 認証を簡単に使用できることを意味します。

10.19.3 Tomcat で SSL をセットアップする

Spring Security プロジェクトの samples/certificate ディレクトリには、いくつかの事前生成された証明書があります。独自に生成したくない場合は、これらを使用してテスト用に SSL を有効にすることができます。ファイル server.jks には、サーバー証明書、秘密鍵、発行認証局証明書が含まれています。サンプルアプリケーションのユーザー向けのクライアント証明書ファイルもいくつかあります。これらをブラウザーにインストールして、SSL クライアント認証を有効にすることができます。

SSL サポートを使用して Tomcat を実行するには、server.jks ファイルを Tomcat conf ディレクトリにドロップし、次のコネクターを server.xml ファイルに追加する

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
            clientAuth="true" sslProtocol="TLS"
            keystoreFile="${catalina.home}/conf/server.jks"
            keystoreType="JKS" keystorePass="password"
            truststoreFile="${catalina.home}/conf/server.jks"
            truststoreType="JKS" truststorePass="password"
/>

クライアントが証明書を提供しなくても SSL 接続を成功させたい場合は、clientAuth を want に設定することもできます。証明書を提示しないクライアントは、フォーム認証などの非 X.509 認証メカニズムを使用しない限り、Spring Security で保護されたオブジェクトにアクセスできません。

10.20 Run-As 認証の置き換え

10.20.1 概要

AbstractSecurityInterceptor は、セキュアオブジェクトコールバックフェーズ中に、SecurityContext および SecurityContextHolder の Authentication オブジェクトを一時的に置き換えることができます。これは、元の Authentication オブジェクトが AuthenticationManager および AccessDecisionManager によって正常に処理された場合にのみ発生します。RunAsManager は、SecurityInterceptorCallback の間に使用される置換 Authentication オブジェクトがあれば、それを示します。

セキュアオブジェクトコールバックフェーズ中に Authentication オブジェクトを一時的に置き換えることにより、セキュアな呼び出しは、異なる認証および認可資格情報を必要とする他のオブジェクトを呼び出すことができます。また、特定の GrantedAuthority オブジェクトの内部セキュリティチェックを実行することもできます。Spring Security は SecurityContextHolder のコンテンツに基づいてリモートプロトコルを自動的に構成する多くのヘルパークラスを提供するため、これらの run-as 置換はリモート Web サービスを呼び出すときに特に役立ちます

10.20.2 構成

RunAsManager インターフェースは、Spring Security によって提供されます。

Authentication buildRunAs(Authentication authentication, Object object,
    List<ConfigAttribute> config);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

最初のメソッドは、メソッド呼び出し中に既存の Authentication オブジェクトを置き換える Authentication オブジェクトを返します。メソッドが null を返す場合、置換を行う必要がないことを示します。2 番目の方法は、構成属性の始動検証の一部として AbstractSecurityInterceptor によって使用されます。supports(Class) メソッドは、セキュリティインターセプターの実装によって呼び出され、構成された RunAsManager がセキュリティインターセプターが提示する型のセキュアオブジェクトをサポートするようにします。

RunAsManager の具体的な実装の 1 つは、Spring Security で提供されます。ConfigAttribute が RUN_AS_ で始まる場合、RunAsManagerImpl クラスは置換 RunAsUserToken を返します。そのような ConfigAttribute が見つかった場合、置換 RunAsUserToken には、元の Authentication オブジェクトと同じプリンシパル、資格情報、付与された権限が、各 RUN_AS_ConfigAttribute の新しい SimpleGrantedAuthority とともに含まれます。新しい SimpleGrantedAuthority にはそれぞれ、接頭辞 ROLE_ が付けられ、その後に RUN_ASConfigAttribute が続きます。例: RUN_AS_SERVER は、ROLE_RUN_AS_SERVER に付与された権限を含む置換 RunAsUserToken になります。

置換 RunAsUserToken は、他の Authentication オブジェクトとまったく同じです。おそらく委譲を通して適切な AuthenticationProvider への AuthenticationManager によって認証される必要があります。RunAsImplAuthenticationProvider はそのような認証を実行します。提示された RunAsUserToken を単に有効なものとして受け入れます。

悪意のあるコードが RunAsUserToken を作成せず、RunAsImplAuthenticationProvider が確実に受け入れられるように提示するために、生成されたすべてのトークンにキーのハッシュが保存されます。RunAsManagerImpl と RunAsImplAuthenticationProvider は、同じキーで Bean コンテキストに作成されます。

<bean id="runAsManager"
    class="org.springframework.security.access.intercept.RunAsManagerImpl">
<property name="key" value="my_run_as_password"/>
</bean>

<bean id="runAsAuthenticationProvider"
    class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider">
<property name="key" value="my_run_as_password"/>
</bean>

同じキーを使用することにより、各 RunAsUserToken は、承認された RunAsManagerImpl によって作成されたことを検証できます。RunAsUserToken は、セキュリティ上の理由から作成後に不変です

10.21 フォームログイン

10.21.1 フォームログイン Java 構成

HTML ファイルや JSP については何もメンションしていないため、ログインを求められたときにログインフォームがどこから来たのか疑問に思うかもしれません。Spring Security のデフォルト設定はログインページの URL を明示的に設定しないため、Spring Security は有効な機能に基づいて自動的に生成し、送信されたログインを処理する URL の標準値を使用して、ユーザーが送信されるデフォルトのターゲット URL ログイン後など。

自動生成されたログインページはすぐに起動して実行するのに便利ですが、ほとんどのアプリケーションは独自のログインページを提供する必要があります。デフォルトの構成を変更したい場合は、次のように拡張することで、前述の WebSecurityConfigurerAdapter をカスタマイズできます。

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

そして、次に示すように configure メソッドをオーバーライドします。

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests
                .anyRequest().authenticated()
        )
        .formLogin(formLogin ->
            formLogin
                .loginPage("/login") 1
                .permitAll()         2
        );
}

1

更新された構成は、ログインページの場所を指定します。

2

ログインページへのアクセスをすべてのユーザー(認証されていないユーザー)に許可する必要があります。formLogin().permitAll() メソッドを使用すると、フォームベースのログインに関連付けられたすべての URL のすべてのユーザーにアクセスを許可できます。

現在の構成の JSP で実装されたログインページの例を以下に示します。

[Note] メモ

以下のログインページは、現在の構成を表しています。デフォルトの一部がニーズを満たさない場合、構成を簡単に更新できます。

<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post">       1
    <c:if test="${param.error != null}">        2
        <p>
            Invalid username and password.
        </p>
    </c:if>
    <c:if test="${param.logout != null}">       3
        <p>
            You have been logged out.
        </p>
    </c:if>
    <p>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"/>  4
    </p>
    <p>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"/>  5
    </p>
    <input type="hidden"                        6
        name="${_csrf.parameterName}"
        value="${_csrf.token}"/>
    <button type="submit" class="btn">Log in</button>
</form>

1

/login URL への POST は、ユーザーの認証を試みます

2

クエリパラメーター error が存在する場合、認証が試行され、失敗しました

3

クエリパラメーター logout が存在する場合、ユーザーは正常にログアウトされました

4

ユーザー名は、username という名前の HTTP パラメーターとして存在する必要があります

5

パスワードは、password という名前の HTTP パラメーターとして存在する必要があります

6

「CSRF トークンを含める」が必要です。詳細については、リファレンスのセクション 5.1.1: “ クロスサイトリクエストフォージェリ (CSRF)” セクションを参照してください。

10.21.2 フォームログイン XML 設定

フォームおよび基本ログインオプション

HTML ファイルや JSP については何もメンションしていないため、ログインを求められたときにログインフォームがどこから来たのか疑問に思うかもしれません。実際、ログインページの URL を明示的に設定しなかったため、Spring Security は有効な機能に基づいて自動的に URL を生成し、送信されたログインを処理する URL の標準値を使用して、デフォルトのターゲット URL はユーザーになります。ログイン後などに送信されます。ただし、名前空間は、これらのオプションをカスタマイズできるようにするための十分なサポートを提供します。例: 独自のログインページを提供する場合は、次を使用できます。

<http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

また、追加の intercept-url 要素を追加して、匿名ユーザー [7] および値 IS_AUTHENTICATED_ANONYMOUSLY の処理方法の詳細については AuthenticatedVoter クラスがログインページへのリクエストを利用できるようになっていることに注意してください。] そうでない場合、リクエストはパターン /** に一致し、ログインページ自体にアクセスすることはできません。これは一般的な構成エラーであり、アプリケーションで無限ループが発生します。ログインページが保護されているように見える場合、Spring Security はログに警告を出します。次のようにパターンに個別の http 要素を定義することにより、特定のパターンに一致するすべてのリクエストにセキュリティフィルターチェーンを完全にバイパスさせることもできます。

<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

Spring Security 3.1 から、複数の http 要素を使用して、異なるリクエストパターンに対して個別のセキュリティフィルターチェーン構成を定義できるようになりました。pattern 属性が http 要素から省略されている場合、すべてのリクエストに一致します。安全でないパターンの作成は、この構文の簡単な例です。このパターンでは、パターンが空のフィルターチェーン [8] にマップされます。この新しい構文については、セキュリティフィルターチェーンの章で詳しく説明します。

これらの保護されていないリクエストは、Spring Security の Web 関連の設定や requires-channel などの追加属性を完全に無視することを理解することが重要です。そのため、リクエスト中に現在のユーザーの情報にアクセスしたり、protected メソッドを呼び出したりすることはできませんセキュリティフィルターチェーンを引き続き適用する場合は、代替として access='IS_AUTHENTICATED_ANONYMOUSLY' を使用します。

フォームログインの代わりに基本認証を使用する場合は、構成を次のように変更します。

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
</http>

その後、基本認証が優先され、ユーザーが保護されたリソースにアクセスしようとしたときにログインを促すために使用されます。別の Web ページに埋め込まれたログインフォームなどを使用する場合は、この構成でフォームログインを引き続き使用できます。

10.22 基本認証およびダイジェスト認証

基本認証とダイジェスト認証は、Web アプリケーションで一般的な代替認証メカニズムです。基本認証は、多くの場合、各リクエストで資格情報を渡すステートレスクライアントで使用されます。ブラウザーベースのユーザーインターフェースと Web サービスの両方でアプリケーションが使用されるフォームベース認証と組み合わせて使用することは非常に一般的です。ただし、基本認証はパスワードをプレーンテキストとして送信するため、HTTPS などの暗号化されたトランスポート層でのみ実際に使用する必要があります。

10.22.1 BasicAuthenticationFilter

BasicAuthenticationFilter は、HTTP ヘッダーで提示される基本認証資格情報の処理を担当します。これは、Spring リモーティングプロトコル(Hessian や Burlap など)、および通常のブラウザーユーザーエージェント(Firefox や Internet Explorer など)による呼び出しの認証に使用できます。HTTP 基本認証を管理する標準は RFC 1945、セクション 11 で定義されており、BasicAuthenticationFilter はこの RFC に準拠しています。基本認証は、ユーザーエージェントに非常に広くデプロイされ、実装が非常に簡単であるため(HTTP ヘッダーで指定されたユーザー名: パスワードの Base64 エンコードであるため)、認証への魅力的なアプローチです。

10.22.2 構成

HTTP 基本認証を実装するには、BasicAuthenticationFilter をフィルターチェーンに追加する必要があります。アプリケーションコンテキストには、BasicAuthenticationFilter とその必要なコラボレーターが含まれている必要があります。

<bean id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</bean>

<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<property name="realmName" value="Name Of Your Realm"/>
</bean>

構成された AuthenticationManager は、各認証リクエストを処理します。認証が失敗した場合、構成された AuthenticationEntryPoint が認証プロセスを再試行するために使用されます。通常、フィルターを BasicAuthenticationEntryPoint と組み合わせて使用します。BasicAuthenticationEntryPoint は、HTTP 基本認証を再試行するための適切なヘッダーを持つ 401 レスポンスを返します。認証が成功した場合、結果の Authentication オブジェクトは通常どおり SecurityContextHolder に配置されます。

認証イベントが成功した場合、または HTTP ヘッダーにサポートされている認証リクエストが含まれていないために認証が試行されなかった場合、フィルターチェーンは通常どおり続行します。フィルターチェーンが中断されるのは、認証が失敗して AuthenticationEntryPoint が呼び出された場合のみです。

10.23 DigestAuthenticationFilter

DigestAuthenticationFilter は、HTTP ヘッダーで提示されるダイジェスト認証資格情報を処理できます。ダイジェスト認証は、基本的に認証の弱点の多くを解決しようとします。具体的には、資格情報がクリアテキストで送信されないようにします。Mozilla Firefox や Internet Explorer など、多くのユーザーエージェントがダイジェスト認証をサポートしています。HTTP ダイジェスト認証を管理する標準は RFC 2617 で定義され、RFC 2069 で規定されているダイジェスト認証標準の以前のバージョンを更新します。ほとんどのユーザーエージェントは RFC 2617 を実装します。Spring Security の DigestAuthenticationFilter は、保護された「auth」品質(qop)と互換性があります。ダイジェスト認証は、暗号化されていない HTTP(つまり TLS/HTTPS を使用しない)を使用する必要があり、認証プロセスのセキュリティを最大限に高めたい場合、より魅力的なオプションです。実際、ダイジェスト認証は、RFC 2518 セクション 17.1 に記載されているように、WebDAV プロトコルの必須要件です。

[Note] メモ

ダイジェストは安全とは見なされないため、最新のアプリケーションではダイジェストを使用しないでください。最も明白な問題は、パスワードをプレーンテキスト、暗号化、または MD5 形式で保存する必要があることです。これらのストレージ形式はすべて安全ではないと見なされます。代わりに、一方向の適応パスワードハッシュ(つまり、bCrypt、PBKDF2、SCrypt など)を使用する必要があります。

ダイジェスト認証の中心は「ノンス」です。これは、サーバーが生成する値です。Spring Security のナンスは次の形式を採用しています。

base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime:   The date and time when the nonce expires, expressed in milliseconds
key:              A private key to prevent modification of the nonce token

DigestAuthenticationEntryPoint には、ノンストークンの生成に使用される key を指定するプロパティと、有効期限(デフォルトは 5 分に等しい 300)を決定する nonceValiditySeconds プロパティがあります。ナンスが有効である場合、ダイジェストは、ユーザー名、パスワード、ナンス、リクエストされている URI、クライアント生成ナンス(ユーザーエージェントが各リクエストを生成するランダム値)、レルム名などを含むさまざまな文字列を連結することによって計算されます。次に MD5 ハッシュを実行します。サーバーとユーザーエージェントの両方がこのダイジェスト計算を実行し、含まれる値(パスワードなど)が一致しない場合、異なるハッシュコードが生成されます。Spring Security の実装では、サーバーで生成されたナンスの有効期限が切れただけの場合(ただし、ダイジェストが有効だった場合)、DigestAuthenticationEntryPoint は "stale=true" ヘッダーを送信します。これにより、ユーザーエージェントに(パスワードやユーザー名などが正しいため)ユーザーを邪魔する必要はなく、単に新しいナンスを使用して再試行するように指示します。

DigestAuthenticationEntryPoint の nonceValiditySeconds パラメーターの適切な値は、アプリケーションによって異なります。非常に安全なアプリケーションでは、ナンスに含まれる expirationTime に到達するまで、インターセプトされた認証ヘッダーを使用してプリンシパルを偽装できることに注意する必要があります。これは適切な設定を選択する際の重要な原則ですが、非常に安全なアプリケーションが最初のインスタンスで TLS/HTTPS を介して実行されないことはまれです。

ダイジェスト認証の実装はより複雑であるため、多くの場合ユーザーエージェントの課題があります。例: Internet Explorer は、同じセッションの後続のリクエストで「不透明」トークンを提示できません。そのため、Spring Security フィルターはすべての状態情報を「nonce」トークンにカプセル化します。テストでは、Spring Security の実装は Mozilla Firefox および Internet Explorer で確実に機能し、ノンスタイムアウトなどを正しく処理します。

10.23.1 構成

理論を確認したため、それを使用する方法を見てみましょう。HTTP ダイジェスト認証を実装するには、フィルターチェーンで DigestAuthenticationFilter を定義する必要があります。アプリケーションコンテキストは、DigestAuthenticationFilter とその必要な協力者を定義する必要があります。

<bean id="digestFilter" class=
    "org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="authenticationEntryPoint" ref="digestEntryPoint"/>
<property name="userCache" ref="userCache"/>
</bean>

<bean id="digestEntryPoint" class=
    "org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<property name="realmName" value="Contacts Realm via Digest Authentication"/>
<property name="key" value="acegi"/>
<property name="nonceValiditySeconds" value="10"/>
</bean>

DigestAuthenticationFilter はユーザーのクリアテキストパスワードに直接アクセスする必要があるため、構成された UserDetailsService が必要です。DAO [9] でエンコードされたパスワードを使用している場合、ダイジェスト認証は機能しません。DAO コラボレーターは、UserCache と一緒に、通常 DaoAuthenticationProvider と直接共有されます。authenticationEntryPoint プロパティは DigestAuthenticationEntryPoint でなければならず、DigestAuthenticationFilter はダイジェスト計算のために正しい realmName および key を取得できます。

BasicAuthenticationFilter と同様に、認証が成功した場合、Authentication リクエストトークンが SecurityContextHolder に配置されます。認証イベントが成功した場合、または HTTP ヘッダーにダイジェスト認証リクエストが含まれていなかったために認証が試行されなかった場合、フィルターチェーンは通常どおり続行します。前の段落で説明したように、フィルターチェーンが中断されるのは、認証が失敗して AuthenticationEntryPoint が呼び出された場合のみです。

ダイジェスト認証の RFC は、セキュリティをさらに高めるためのさまざまな追加機能を提供します。例: ナンスはリクエストごとに変更できます。それにもかかわらず、Spring Security の実装は、実装の複雑さ(および明らかになるユーザーエージェントの非互換性)を最小限に抑え、サーバー側の状態を保存する必要を回避するように設計されます。これらの機能をさらに詳しく調べたい場合は、RFC 2617 を確認してください。私たちの知る限り、Spring Security の実装は、この RFC の最小標準に準拠しています。

10.24 ログアウトの処理

10.24.1 ログアウト Java 構成

WebSecurityConfigurerAdapter (Javadoc) を使用すると、ログアウト機能が自動的に適用されます。デフォルトでは、URL /logout にアクセスすると、次の方法でユーザーがログアウトされます。

  • HTTP セッションの無効化
  • 構成された RememberMe 認証をクリーンアップする
  • SecurityContextHolder のクリア
  • /login?logout にリダイレクト

ただし、ログイン機能の構成と同様に、ログアウト要件をさらにカスタマイズするためのさまざまなオプションもあります。

protected void configure(HttpSecurity http) throws Exception {
    http
        .logout(logout ->                                                       1
            logout
                .logoutUrl("/my/logout")                                        2
                .logoutSuccessUrl("/my/index")                                  3
                .logoutSuccessHandler(logoutSuccessHandler)                     4
                .invalidateHttpSession(true)                                    5
                .addLogoutHandler(logoutHandler)                                6
                .deleteCookies(cookieNamesToClear)                              7
        )
        ...
}

1

ログアウトのサポートを提供します。WebSecurityConfigurerAdapter を使用する場合、これは自動的に適用されます。

2

ログアウトをトリガーする URL(デフォルトは /logout)。CSRF 保護が有効になっている場合(デフォルト)、リクエストは POST である必要があります。詳細については、JavaDoc (Javadoc) を参照してください。

3

ログアウト後にリダイレクトする URL。デフォルトは /login?logout です。詳細については、JavaDoc (Javadoc) を参照してください。

4

カスタム LogoutSuccessHandler を指定してみましょう。これが指定されている場合、logoutSuccessUrl() は無視されます。詳細については、JavaDoc (Javadoc) を参照してください。

5

ログアウト時に HttpSession を無効にするかどうかを指定します。これはデフォルトでです。カバーに SecurityContextLogoutHandler を構成します。詳細については、JavaDoc (Javadoc) を参照してください。

6

LogoutHandler を追加します。SecurityContextLogoutHandler は、デフォルトで最後の LogoutHandler として追加されます。

7

ログアウトの成功時に削除する Cookie の名前を指定できます。これは、CookieClearingLogoutHandler を明示的に追加するためのショートカットです。

[Note] メモ

=== もちろん、XML 名前空間表記を使用してログアウトを構成することもできます。詳細については、Spring Security XML 名前空間セクションのログアウト要素のドキュメントを参照してください。===

一般に、ログアウト機能をカスタマイズするために、LogoutHandler (Javadoc)  および / または LogoutSuccessHandler (Javadoc)  実装を追加できます。多くの一般的なシナリオでは、これらのハンドラーは、Fluent API を使用するときに隠れて適用されます。

10.24.2 ログアウト XML 設定

logout 要素は、特定の URL にナビゲートすることでログアウトのサポートを追加します。デフォルトのログアウト URL は /logout ですが、logout-url 属性を使用して他の URL に設定できます。他の利用可能な属性の詳細については、ネームスペースの付録を参照してください。

10.24.3 LogoutHandler

一般的に、LogoutHandler (Javadoc)  実装は、ログアウト処理に参加できるクラスを示します。これらは、必要なクリーンアップを実行するために呼び出されることが期待されています。そのため、例外をスローすべきではありません。さまざまな実装が提供されます。

詳細については、セクション 10.13.4: “Remember-Me インターフェースと実装 ” を参照してください。

LogoutHandler 実装を直接提供する代わりに、流れるような API は、それぞれの LogoutHandler 実装をカバーするショートカットを提供します。例: deleteCookies() では、ログアウト成功時に削除される 1 つ以上の Cookie の名前を指定できます。これは、CookieClearingLogoutHandler の追加と比較したショートカットです。

10.24.4 LogoutSuccessHandler

LogoutSuccessHandler は、LogoutFilter によるログアウトの成功後に呼び出され、たとえば適切な宛先へのリダイレクトまたは転送。インターフェースは LogoutHandler とほぼ同じですが、例外が発生する可能性があることに注意してください。

次の実装が提供されます。

上記のように、SimpleUrlLogoutSuccessHandler を直接指定する必要はありません。代わりに、流れるような API は logoutSuccessUrl() を設定することによりショートカットを提供します。これにより、SimpleUrlLogoutSuccessHandler がカバーにセットアップされます。指定された URL は、ログアウトが発生した後にリダイレクトされます。デフォルトは /login?logout です。

HttpStatusReturningLogoutSuccessHandler は、REST API タイプのシナリオで興味深い場合があります。ログアウトが成功したときに URL にリダイレクトする代わりに、この LogoutSuccessHandler では、返されるプレーン HTTP ステータスコードを提供できます。設定されていない場合、デフォルトでステータスコード 200 が返されます。

10.24.5 その他のログアウト関連の参照

10.25 カスタム AuthenticationEntryPoint の設定

フォームログイン、OpenID、または名前空間を介した基本認証を使用していない場合、従来の Bean 構文を使用して認証フィルターとエントリポイントを定義し、先ほど見たように名前空間にリンクすることができます。対応する AuthenticationEntryPoint は、<http> 要素の entry-point-ref 属性を使用して設定できます。

CAS サンプルアプリケーションは、この構文を含む名前空間でカスタム Bean を使用する良い例です。認証エントリポイントに詳しくない場合は、技術概要の章で説明します。



[2] これは、member={0} を使用する基礎となる DefaultLdapAuthoritiesPopulator のデフォルト構成とは異なることに注意してください。

[3] DNS ルックアップを使用してサーバーの IP アドレスを取得することもできます。これは現在サポートされていませんが、将来のバージョンでサポートされる予定です。

認証リクエスト中にフィルターが呼び出されないため、認証後にリダイレクトを実行するメカニズム(フォームログインなど)による [4] 認証は SessionManagementFilter によって検出されません。これらの場合、セッション管理機能を個別に処理する必要があります。

[5] 基本的に、有効なログイン名が不必要に公開されるのを防ぐため、ユーザー名は Cookie に含まれません。これについては、この記事のコメントセクションで説明しています。

[6]key プロパティの使用は、ここで実際のセキュリティを提供するものと見なされるべきではありません。これは単なる記録管理の練習です。認証クライアントが Authentication オブジェクトを構築できるシナリオ(たとえば、RMI 呼び出しなど)で AnonymousAuthenticationProvider を含む ProviderManager を共有している場合、悪意のあるクライアントは自身が作成した AnonymousAuthenticationToken を送信できます(選択された状態で)ユーザー名と権限リスト)。key が推測可能であるか、見つけられる場合、トークンは匿名プロバイダーによって受け入れられます。これは通常の使用では問題ありませんが、RMI を使用している場合は、HTTP 認証メカニズムに使用するプロバイダーを共有するのではなく、匿名プロバイダーを省略するカスタマイズされた ProviderManager を使用するのが最善です。

[8] 複数の <http> 要素の使用は重要な機能であり、たとえば、ネームスペースが同じアプリケーション内でステートフルパスとステートレスパスの両方を同時にサポートできるようにします。intercept-url 要素で属性 filters="none" を使用する以前の構文は、この変更と互換性がなく、3.1 ではサポートされなくなりました。

[9]DigestAuthenticationFilter.passwordAlreadyEncoded が true に設定されている場合、HEX(MD5(username:realm:password))の形式でパスワードをエンコードすることができます。ただし、他のパスワードエンコードはダイジェスト認証では機能しません。

現行バージョンへ切り替える