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; }
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")); }
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 (英語) を参照できます。
Spring Security の LDAP 認証は、大まかに次の段階に分けることができます。
uid=joe,ou=users,dc=spring,dc=io
などの完全な DN になります。例外は、LDAP ディレクトリがユーザー情報を取得してローカルで認証するために使用されている場合です。これは、ユーザーパスワードなどの属性に対する読み取りアクセスが制限されてディレクトリが設定されることが多いため、不可能な場合があります。
以下にいくつかの構成シナリオを見ていきます。使用可能な構成オプションの詳細については、セキュリティ名前空間スキーマ(XML エディターで使用可能な情報)を参照してください。
最初に行う必要があるのは、認証が行われるサーバーを構成することです。これは、セキュリティ名前空間の <ldap-server>
要素を使用して行われます。これは、url
属性を使用して、外部 LDAP サーバーを指すように構成できます。
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
![]() | メモ |
---|---|
|
<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 サンプルアプリケーションから取得できます。
これは最も一般的な 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
が提供されない場合、検索はルートから実行されます。
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 を参照してください。
上記で使用した名前空間設定オプションは、Spring Bean を明示的に使用するよりも使いやすく、はるかに簡潔です。アプリケーションコンテキストで Spring Security LDAP を直接構成する方法を知る必要がある場合があります。たとえば、一部のクラスの動作をカスタマイズできます。名前空間の設定を使用して満足している場合は、このセクションと次のセクションをスキップできます。
メインの LDAP プロバイダークラスである LdapAuthenticationProvider
は、実際にはあまり機能しませんが、ユーザーを認証し、ユーザーの GrantedAuthority
のセットをそれぞれ取得する LdapAuthenticator
と LdapAuthoritiesPopulator
の 2 つの他の Bean に作業を委譲します。
オーセンティケーターは、必要なユーザー属性を取得する責任もあります。これは、属性に対するアクセス許可が使用されている認証の種類に依存する可能性があるためです。例: ユーザーとしてバインドする場合は、ユーザー自身の権限で読み取る必要がある場合があります。
現在、Spring Security には 2 つの認証戦略が用意されています。
(いずれかの方法で)ユーザーを認証する前に、アプリケーションに提供されたログイン名から識別名(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 が見つからない場合は検索が使用されます。
パッケージ org.springframework.security.ldap.authentication
のクラス BindAuthenticator
は、バインド認証戦略を実装します。ユーザーとしてバインドしようとするだけです。
上記の Bean は、サーバーに接続できる必要があります。両方に Spring LDAP の ContextSource
の拡張である SpringSecurityContextSource
を提供する必要があります。特別な要件がない限り、通常 DefaultSpringSecurityContextSource
Bean を構成します。これは、LDAP サーバーの URL と、オプションでサーバーにバインドするときにデフォルトで使用される「マネージャー」ユーザーのユーザー名とパスワードで構成できます(匿名でバインドする代わりに)。詳細については、このクラスおよび Spring LDAP の AbstractContextSource
の Javadoc を参照してください。
多くの場合、ディレクトリ内のユーザーエントリを見つけるには、単純な DN マッチングよりも複雑な戦略が必要です。これを LdapUserSearch
インスタンスにカプセル化して、オーセンティケーターの実装に提供して、ユーザーがユーザーを見つけられるようにすることができます。提供される実装は FilterBasedLdapUserSearch
です。
この Bean は、LDAP フィルターを使用して、ディレクトリ内のユーザーオブジェクトを照合します。このプロセスは、JDK DirContext クラス (英語) の対応する検索メソッドの Javadoc で説明されています。そこで説明したように、検索フィルターにはパラメーターを指定できます。このクラスの場合、有効なパラメーターは {0}
のみで、これはユーザーのログイン名に置き換えられます。
ユーザーを正常に認証した後、LdapAuthenticationProvider
は、構成された LdapAuthoritiesPopulator
Bean を呼び出すことにより、ユーザーの一連の権限をロードしようとします。DefaultLdapAuthoritiesPopulator
は、ユーザーがメンバーになっているグループをディレクトリで検索することにより、権限をロードする実装です(通常、これらはディレクトリ内の groupOfNames
または groupOfUniqueNames
エントリになります)。動作の詳細については、このクラスの Javadoc を参照してください。
LDAP を認証のみに使用し、異なるソース(データベースなど)から機関をロードする場合は、このインターフェースの独自の実装を提供し、代わりにそれを挿入できます。
ここで説明したいくつかの 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 を取得します。
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
から取得した標準コンテキストを使用してデータを読み取ります(ユーザーを見つけるための検索が構成されている場合、これはデータになります。検索オブジェクトによって返されます)。
Active Directory は独自の非標準の認証オプションをサポートしており、通常の使用パターンは標準の LdapAuthenticationProvider
にはあまり合いません。通常、認証は、LDAP 識別名を使用するのではなく、ドメインユーザー名([email protected] (英語)
形式)を使用して実行されます。これを簡単にするために、Spring Security 3.1 には、一般的な Active Directory セットアップ用にカスタマイズされた認証プロバイダーがあります。
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
オブジェクトになる機関を制御することもできます。
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
カスタム AuthenticationProvider
を Bean として公開することにより、カスタム認証を定義できます。例: 以下は、SpringAuthenticationProvider
が AuthenticationProvider
を実装すると仮定して認証をカスタマイズします。
![]() | メモ |
---|---|
これは、 |
@Bean public SpringAuthenticationProvider springAuthenticationProvider() { return new SpringAuthenticationProvider(); }
実際には、アプリケーションコンテキストファイルにいくつかの名前を追加するよりも、よりスケーラブルなユーザー情報のソースが必要になります。ほとんどの場合、ユーザー情報をデータベースや 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: “ 認証マネージャーと名前空間 ” を参照してください。
カスタム UserDetailsService
を Bean として公開することにより、カスタム認証を定義できます。例: 以下は、SpringDataUserDetailsService
が UserDetailsService
を実装すると仮定して認証をカスタマイズします。
![]() | メモ |
---|---|
これは、 |
@Bean public SpringDataUserDetailsService springDataUserDetailsService() { return new SpringDataUserDetailsService(); }
PasswordEncoder
を Bean として公開することにより、パスワードのエンコード方法をカスタマイズすることもできます。例: bcrypt を使用する場合、以下に示すように Bean 定義を追加できます。
@Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
Spring Security の PasswordEncoder
インターフェースは、パスワードの安全な保存を可能にするために、パスワードの一方向変換を実行するために使用されます。PasswordEncoder
は一方向の変換であるため、パスワード変換を双方向にする必要がある場合(つまり、データベースへの認証に使用される資格情報を保存する場合)は意図されていません。通常、PasswordEncoder
は、認証時にユーザーが指定したパスワードと比較する必要があるパスワードを保存するために使用されます。
長年にわたって、パスワードを保存するための標準的なメカニズムが進化してきました。最初は、パスワードはプレーンテキストで保存されていました。パスワードは、データストアにアクセスするために必要な資格情報で保存されているため、パスワードは安全であると想定されていました。しかし、悪意のあるユーザーは、SQL インジェクションなどの攻撃を使用して、ユーザー名とパスワードの大きな「データダンプ」を取得する方法を見つけることができました。ますます。多くのユーザー資格情報がパブリックセキュリティの専門家になるにつれて、ユーザーのパスワードを保護するためにより多くのことを行う必要があることに気付きました。
開発者は、SHA-256 などの一方向ハッシュを介してパスワードを実行した後、パスワードを保存することが推奨されました。ユーザーが認証を試行すると、ハッシュされたパスワードは、入力したパスワードのハッシュと比較されます。つまり、システムはパスワードの一方向ハッシュを保存するだけで済みました。違反が発生した場合、パスワードの一方向ハッシュのみが公開されました。ハッシュは一方向であり、ハッシュが与えられたパスワードを推測することは計算上困難であったため、システム内の各パスワードを把握する努力は価値がありません。この新しいシステムを無効にするために、悪意のあるユーザーはレインボーテーブル (英語) として知られるルックアップテーブルを作成することにしました。各パスワードを毎回推測する作業を行うのではなく、パスワードを一度計算してルックアップテーブルに保存しました。
レインボーテーブルの有効性を緩和するために、開発者はソルトパスワードを使用することが推奨されます。ハッシュ関数への入力としてパスワードだけを使用する代わりに、ランダムなバイト(ソルトと呼ばれる)がすべてのユーザーのパスワードに対して生成されます。ソルトとユーザーのパスワードは、一意のハッシュを生成するハッシュ関数を介して実行されます。ソルトは、ユーザーのパスワードとともにクリアテキストで保存されます。次に、ユーザーが認証を試みると、ハッシュされたパスワードは、保存されたソルトのハッシュと入力したパスワードと比較されます。独自のソルトは、ソルトとパスワードの組み合わせごとにハッシュが異なるため、レインボーテーブルが効果的ではなくなったことを意味します。
現代では、暗号化ハッシュ(SHA-256 など)はもはや安全ではないことがわかります。その理由は、最新のハードウェアを使用すると、1 秒間に何十億ものハッシュ計算を実行できるからです。これは、各パスワードを簡単に個別に解読できることを意味します。
開発者は、適応型一方向機能を利用してパスワードを保存することが推奨されています。適応型一方向機能によるパスワードの検証は、意図的にリソース(CPU、メモリなど)を集中的に使用します。適応型一方向機能により、ハードウェアが向上するにつれて成長する「作業要素」を構成できます。システムのパスワードを確認するのに約 1 秒かかるように「作業要素」を調整することをお勧めします。このトレードオフは、攻撃者がパスワードを解読することを困難にすることですが、それほど高負荷ではなく、あなた自身のシステムに過度の負担をかけます。Spring Security は「作業要素」の適切な出発点を提供しようとしましたが、パフォーマンスはシステムによって大きく異なるため、ユーザーは自分のシステムの「作業要素」をカスタマイズすることをお勧めします。使用する必要がある適応型一方向関数の例には、bcrypt (英語) 、PBKDF2 (英語) 、scrypt (英語) 、および Argon2 (英語) が含まれます。
適応型一方向機能は意図的にリソースを集中的に使用するため、すべてのリクエストに対してユーザー名とパスワードを検証すると、アプリケーションのパフォーマンスが大幅に低下します。検証リソースを集中的に使用することでセキュリティが得られるため、Spring Security(または他のライブラリ)がパスワードの検証を高速化するためにできることはありません。ユーザーは、長期資格情報(つまり、ユーザー名とパスワード)を短期資格情報(つまり、セッション、OAuth トークンなど)と交換することをお勧めします。セキュリティを損なうことなく、短期間の資格情報を迅速に検証できます。
Spring Security 5.0 以前は、デフォルトの PasswordEncoder
は NoOpPasswordEncoder
で、プレーンテキストのパスワードが必要でした。パスワード履歴セクションに基づいて、デフォルトの PasswordEncoder
が BCryptPasswordEncoder
のようになっていることを期待するかもしれません。ただし、これは 3 つの実際の問題を無視します。
代わりに、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{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
最初のパスワードは、 | |
2 番目のパスワードには、 | |
3 番目のパスワードの | |
4 番目のパスワードは、 | |
最終パスワードには、 |
![]() | メモ |
---|---|
一部のユーザーは、ストレージ形式が潜在的なハッカーに提供されていることを懸念する場合があります。パスワードの保存はアルゴリズムがシークレットであることに依存していないため、これは懸念事項ではありません。さらに、攻撃者がプレフィックスなしで簡単に把握できる形式はほとんどあります。例: BCrypt パスワードは、多くの場合 |
コンストラクターに渡される 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 構成を使用している場合は、次のような構成を作成できます。
![]() | 警告 |
---|---|
|
@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 を参照してください。
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));
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));
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));
SCryptPasswordEncoder
実装は、暗号化 (英語) アルゴリズムを使用してパスワードをハッシュします。カスタムハードウェアでのパスワードクラッキングを無効にするために、scrypt は大量のメモリを必要とする故意に遅いアルゴリズムです。他の適応型一方向機能と同様に、システムのパスワードを確認するのに約 1 秒かかるように調整する必要があります。
// Create an encoder with all the defaults SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
下位互換性のために完全に存在する他の PasswordEncoder
実装が多数あります。これらはすべて、もはや安全であると見なされないことを示すために非推奨です。ただし、既存のレガシーシステムを移行することは難しいため、削除する計画はありません。
パスワードは、その目的のために設計された安全なハッシュアルゴリズムを使用して常にエンコードする必要があります(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 などのより安全なオプションへの移行を検討する必要があります。
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>
HTTP セッションに関連する機能は、フィルターが委譲する SessionManagementFilter
と SessionAuthenticationStrategy
インターフェースの組み合わせによって処理されます。典型的な使用箇所には、セッション固定保護攻撃防止、セッションタイムアウトの検出、および認証済みユーザーが同時に開くことができるセッション数の制限が含まれます。
Spring Security を構成して、無効なセッション ID の送信を検出し、ユーザーを適切な URL にリダイレクトできます。これは、session-management
要素によって実現されます。
<http> ... <session-management invalid-session-url="/invalidSession.htm" /> </http>
このメカニズムを使用してセッションタイムアウトを検出した場合、ユーザーがログアウトしてからブラウザーを閉じずに再度ログインすると、誤ってエラーが報告される可能性があることに注意してください。これは、セッションを無効にしてもセッション Cookie がクリアされず、ユーザーがログアウトしても再送信されるためです。ログアウトハンドラーで次の構文を使用するなどして、ログアウト時に JSESSIONID Cookie を明示的に削除できる場合があります。
<http> <logout delete-cookies="JSESSIONID" /> </http>
残念ながら、これはすべてのサーブレットコンテナーで動作することを保証できないため、環境でテストする必要があります。
![]() | メモ |
---|---|
=== プロキシの背後でアプリケーションを実行している場合は、プロキシサーバーを構成してセッション Cookie を削除することもできます。例: Apache HTTPD の mod_headers を使用すると、次のディレクティブは、ログアウトリクエストへのレスポンスで |
<LocationMatch "/tutorial/logout"> Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT" </LocationMatch>
===
アプリケーションにログインするための 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
要素に追加できます。
フォームベースのログインにカスタマイズされた認証フィルターを使用している場合、同時セッション制御サポートを明示的に構成する必要があります。詳細については、セッション管理の章を参照してください。
セッション固定 (英語) 攻撃は、悪意のある攻撃者がサイトにアクセスしてセッションを作成し、同じセッションでログインするように別のユーザーを誘導する可能性がある潜在的なリスクです(たとえば、セッション識別子をパラメーターとして含むリンクを送信することにより))。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 が通知される結果、その使用は注意します。追加情報については、セッション管理の章を参照してください。
SessionManagementFilter
は、SecurityContextRepository
のコンテンツを SecurityContextHolder
の現在のコンテンツと照合して、通常、事前認証や remember-me [4] などの非対話型認証メカニズムによって現在のリクエスト中にユーザーが認証されたかどうかを判断します。リポジトリにセキュリティコンテキストが含まれている場合、フィルターは何もしません。存在せず、スレッドローカル SecurityContext
に(非匿名の) Authentication
オブジェクトが含まれる場合、フィルターは、スタック内の前のフィルターによって認証されたと見なします。次に、構成された SessionAuthenticationStrategy
を呼び出します。
ユーザーが現在認証されていない場合、フィルターは無効なセッション ID がリクエストされたかどうかを確認し(タイムアウトなど)、構成されている InvalidSessionStrategy
(設定されている場合)を呼び出します。最も一般的な動作は、固定 URL にリダイレクトすることであり、これは標準実装 SimpleRedirectInvalidSessionStrategy
にカプセル化されます。後者は、前述のように、ネームスペースを介して無効なセッション URL を構成するときにも使用されます。
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 を参照してください。
Spring Security は、プリンシパルが指定された回数を超えて同じアプリケーションに対して同時に認証するのを防ぐことができます。多くの ISV はこれを利用してライセンスを強制しますが、ネットワーク管理者がこの機能を好むのは、ユーザーがログイン名を共有できないようにするためです。たとえば、ユーザー "Batman" が 2 つの異なるセッションから Web アプリケーションにログオンするのを停止できます。以前のログインを期限切れにするか、再度ログインしようとしたときにエラーを報告して、2 回目のログインを防ぐことができます。2 番目の方法を使用している場合、明示的にログアウトしていないユーザー(たとえば、ブラウザーを閉じたばかりのユーザー)は、元のセッションが期限切れになるまで再度ログインできないことに注意してください。
同時実行制御はネームスペースでサポートされているため、最も単純な構成については、以前のネームスペースの章を確認してください。ただし、場合によってはカスタマイズする必要があります。
実装では、ConcurrentSessionControlAuthenticationStrategy
と呼ばれる SessionAuthenticationStrategy
の特殊バージョンを使用します。
![]() | メモ |
---|---|
以前は、同時認証チェックは |
同時セッションのサポートを使用するには、以下を 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
に通知できるため、これは重要です。これがないと、ユーザーは、別のセッションからログアウトしたりタイムアウトしたりしても、セッションの許容量を超えると再びログインできなくなります。
名前空間またはプレーン Bean を使用して同時実行制御を設定すると、アプリケーション内で直接使用できる SessionRegistry
への参照が提供されるという便利な副作用があります。ユーザーが持つ可能性のあるセッションについては、インフラストラクチャをセットアップする価値があるかもしれません。maximumSession
プロパティを -1 に設定して、無制限のセッションを許可できます。名前空間を使用している場合は、session-registry-alias
属性を使用して内部で作成された SessionRegistry
のエイリアスを設定し、独自の Bean に注入できる参照を提供できます。
getAllPrincipals()
メソッドは、現在認証されているユーザーのリストを提供します。ユーザーのセッションをリストするには、getAllSessions(Object principal, boolean includeExpiredSessions)
メソッドを呼び出して、SessionInformation
オブジェクトのリストを返します。SessionInformation
インスタンスで expireNow()
を呼び出すことにより、ユーザーのセッションを期限切れにすることもできます。ユーザーがアプリケーションに戻ると、ユーザーは続行できなくなります。たとえば、これらのメソッドは管理アプリケーションで役立ちます。詳細については、Javadoc を参照してください。
Remember-me 認証または永続ログイン認証とは、セッション間でプリンシパルの ID を記憶できる Web サイトを指します。これは通常、Cookie をブラウザーに送信することで実現されます。Cookie は今後のセッションで検出され、自動ログインが行われます。Spring Security は、これらの操作を実行するために必要なフックを提供し、具体的な remember-me 実装を 2 つ備えています。1 つはハッシュを使用して Cookie ベースのトークンのセキュリティを保持し、もう 1 つはデータベースまたはその他の永続的なストレージメカニズムを使用して、生成されたトークンを保存します。
両方の実装が UserDetailsService
を必要とすることに注意してください。UserDetailsService
を使用しない認証プロバイダー(LDAP プロバイダーなど)を使用している場合、アプリケーションコンテキストに UserDetailsService
Bean が含まれていない限り機能しません。
このアプローチでは、ハッシュを使用して便利な 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 の名前です。
このアプローチは、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)
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 つの実装を提供することを確認しました。これらを順番に見ていきます。
この実装は、セクション 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
の直後)。
このクラスは TokenBasedRememberMeServices
と同じ方法で使用できますが、トークンを保存するには PersistentTokenRepository
でさらに構成する必要があります。2 つの標準実装があります。
InMemoryTokenRepositoryImpl
。JdbcTokenRepositoryImpl
。データベーススキーマは、上記のセクション 10.13.3: “ 永久トークンアプローチ ” で説明されています。
名前空間は、通常のフォームベースのログインの代わりに、またはそれに加えて、簡単な変更で 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 を選択することもできます。詳細については、認証プロバイダーに関する前のセクションを参照してください。このユーザーデータのセットはユーザーの権限を読み込むためにのみ使用されるため、上記のユーザー構成からパスワード属性を省略していることに注意してください。ランダムなパスワードが内部で生成されるため、このユーザーデータを構成の他の場所で認証ソースとして誤って使用することを防ぎます。
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 プロバイダーに異なる属性リストが提供されています。
一般に、許可するものと明示的に禁止するものを明示的に指定する「デフォルトで拒否」を採用することは、優れたセキュリティ慣行と考えられています。特に Web アプリケーションの場合、認証されていないユーザーがアクセスできるものを定義することも同様の状況です。多くのサイトでは、いくつかの URL 以外(ホームページやログインページなど)についてユーザーを認証する必要があります。この場合、すべての保護されたリソースに対してではなく、これらの特定の URL に対してアクセス構成属性を定義するのが最も簡単です。別の言い方をすると、ROLE_SOMETHING
がデフォルトで必要であり、アプリケーションのログイン、ログアウト、ホームページなど、このルールの特定の例外のみを許可すると言うのはいいことです。これらのページをフィルターチェーンから完全に省略して、アクセス制御チェックをバイパスすることもできますが、他の理由、特に認証されたユーザーに対してページの動作が異なる場合、これは望ましくない場合があります。
これが匿名認証の意味です。「匿名で認証された」ユーザーと認証されていないユーザーの間に実際の概念上の違いはないことに注意してください。Spring Security の匿名認証は、アクセス制御属性を構成するより便利な方法を提供するだけです。たとえば、getCallerPrincipal
などのサーブレット API 呼び出しの呼び出しは、実際には SecurityContextHolder
に匿名認証オブジェクトが存在する場合でも null を返します。
監査インターセプターが SecurityContextHolder
を照会して、特定の操作を担当したプリンシパルを識別する場合など、匿名認証が役立つ状況が他にもあります。SecurityContextHolder
が常に Authentication
オブジェクトを含み、決して null
を含まないことがわかっている場合、クラスをより堅牢に作成できます。
匿名認証のサポートは、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>
匿名認証の議論を締めくくるのは、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
によって処理されます。
Spring Security を認可に使用したい場合もありますが、ユーザーはアプリケーションにアクセスする前に外部システムによってすでに確実に認証されています。これらの状況を「事前認証済み」シナリオと呼びます。例には、X.509、Siteminder、アプリケーションが実行されている Java EE コンテナーによる認証が含まれます。事前認証を使用する場合、Spring Security は
詳細は、外部認証メカニズムによって異なります。ユーザーは、X.509 の場合は証明書情報によって、Siteminder の場合は HTTP リクエストヘッダーによって識別されます。コンテナー認証に依存している場合、ユーザーは受信 HTTP リクエストで getUserPrincipal()
メソッドを呼び出すことにより識別されます。場合によっては、外部メカニズムがユーザーにロール / 権限情報を提供することがありますが、別の場合は、UserDetailsService
などの別のソースから権限を取得する必要があります。
ほとんどの事前認証メカニズムは同じパターンに従うため、Spring Security には事前認証された認証プロバイダーを実装するための内部フレームワークを提供するクラスのセットがあります。これにより、重複がなくなり、新しい実装を構造化された方法で追加できるようになり、すべてをゼロから記述する必要がなくなります。X.509 認証のようなものを使用する場合、これらのクラスについて知る必要はありません。すでに使用しやすく、使いやすい名前空間設定オプションがすでにあるためです。明示的な Bean 構成を使用する必要がある場合、または独自の実装の作成を計画している場合は、提供された実装がどのように機能するかを理解しておくと役立ちます。org.springframework.security.web.authentication.preauth
にクラスがあります。ここで概要を説明するだけなので、必要に応じて Javadoc とソースを参照してください。
このクラスは、セキュリティコンテキストの現在の内容をチェックし、空の場合、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
インターフェースを実装します。これにより、認証プロバイダーは、ユーザーに外部的に割り当てられた権限を読み取ることができます。次に具体的な例を見てみましょう。
このクラスのインスタンスである authenticationDetailsSource
でフィルターが構成されている場合、権限情報は、「マップ可能なロール」の所定のセットごとに isUserInRole(String role)
メソッドを呼び出すことによって取得されます。クラスは、構成された MappableAttributesRetriever
からこれらを取得します。可能な実装には、アプリケーションコンテキストでリストをハードコードし、web.xml
ファイルの <security-role>
情報からロール情報を読み取ることが含まれます。事前認証サンプルアプリケーションは、後者のアプローチを使用します。
構成された Attributes2GrantedAuthoritiesMapper
を使用して、ロール(または属性)が Spring Security GrantedAuthority
オブジェクトにマップされる追加の段階があります。デフォルトでは、通常の ROLE_
プレフィックスが名前に追加されますが、動作を完全に制御できます。
事前認証されたプロバイダーは、ユーザーのために UserDetails
オブジェクトをロードする以上のことはほとんどありません。AuthenticationUserDetailsService
に委譲することでこれを行います。後者は標準の UserDetailsService
に似ていますが、単なるユーザー名ではなく Authentication
オブジェクトを取ります。
public interface AuthenticationUserDetailsService { UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException; }
このインターフェースには他の用途もありますが、事前認証を使用すると、前のセクションで見たように、Authentication
オブジェクトにパッケージ化された機関にアクセスできます。PreAuthenticatedGrantedAuthoritiesUserDetailsService
クラスがこれを行います。または、UserDetailsByNameServiceWrapper
実装を介して標準 UserDetailsService
に委譲することもできます。
AuthenticationEntryPoint
については、技術概要の章で説明しました。通常、認証されていないユーザー(保護されたリソースにアクセスしようとした場合)の認証プロセスを開始するロールを果たしますが、事前認証された場合は適用されません。事前認証を他の認証メカニズムと組み合わせて使用していない場合にのみ、このクラスのインスタンスで ExceptionTranslationFilter
を構成します。ユーザーが AbstractPreAuthenticatedProcessingFilter
によって拒否され、認証が null になった場合に呼び出されます。呼び出されると、常に 403
-forbidden レスポンスコードを返します。
X.509 認証はそれ自身の章でカバーされています。ここでは、事前に認証された他のシナリオをサポートするクラスをいくつか見ていきます。
外部認証システムは、HTTP リクエストに特定のヘッダーを設定することにより、アプリケーションに情報を提供できます。このよく知られた例は Siteminder で、SM_USER
というヘッダーでユーザー名を渡します。このメカニズムは、RequestHeaderAuthenticationFilter
クラスによってサポートされており、ヘッダーからユーザー名を抽出するだけです。デフォルトでは、名前 SM_USER
がヘッダー名として使用されます。詳細については、Javadoc を参照してください。
![]() | ヒント |
---|---|
このようなシステムを使用する場合、フレームワークは認証チェックをまったく実行しないため、外部システムが適切に構成され、アプリケーションへのすべてのアクセスを保護することが非常に重要です。攻撃者がこれを検出せずに元のリクエストのヘッダーを偽造できる場合、希望するユーザー名を選択する可能性があります。 |
このフィルターを使用した典型的な構成は次のようになります。
<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」と呼ばれる)を追加したことを前提としています。
クラス J2eePreAuthenticatedProcessingFilter
は、HttpServletRequest
の userPrincipal
プロパティからユーザー名を抽出します。このフィルターの使用は、通常、「J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource」と呼ばれるセクションで説明したように、 Java EE ロールの使用と組み合わされます。
このアプローチを使用するコードベースにはサンプルアプリケーションがあります。そのため、github からコードを入手し、興味がある場合はアプリケーションコンテキストファイルを確認してください。コードは samples/xml/preauth
ディレクトリにあります。
Spring Security は、認証リクエストを Java Authentication and Authorization Service(JAAS)に委譲できるパッケージを提供します。このパッケージについては、以下で詳しく説明します。
AbstractJaasAuthenticationProvider
は、提供されている JAAS AuthenticationProvider
実装の基礎です。サブクラスは、LoginContext
を作成するメソッドを実装する必要があります。AbstractJaasAuthenticationProvider
には、以下で説明する多くの依存関係があります。
ほとんどの 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 はプリンシパルと連携します。「ロール」でさえ、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
があります。
DefaultJaasAuthenticationProvider
では、JAAS Configuration
オブジェクトを依存関係として挿入できます。次に、注入された JAAS Configuration
を使用して LoginContext
を作成します。これは、DefaultJaasAuthenticationProvider
が JaasAuthenticationProvider
のように Configuration
の特定の実装にバインドされていないことを意味します。
Configuration
を DefaultJaasAuthenticationProvider
に挿入しやすくするために、InMemoryConfiguration
という名前のデフォルトのメモリ内実装が提供されています。実装コンストラクターは、各キーがログイン構成名を表し、値が AppConfigurationEntry
の Array
を表す Map
を受け入れます。InMemoryConfiguration
は、提供された Map
内でマッピングが見つからない場合に使用される AppConfigurationEntry
オブジェクトのデフォルト Array
もサポートします。詳細については、InMemoryConfiguration
のクラスレベルの javadoc を参照してください。
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>
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>
構成されている場合、JaasApiIntegrationFilter
は JaasAuthenticationToken
で Subject
として実行しようとします。これは、以下を使用して Subject
にアクセスできることを意味します。
Subject subject = Subject.getSubject(AccessController.getContext());
この統合は、jaas-api-provision 属性を使用して簡単に構成できます。この機能は、実装されている JAAS サブジェクトに依存するレガシーまたは外部 API と統合する場合に役立ちます。
JA-SIG は、CAS として知られるエンタープライズ規模のシングルサインオンシステムを作成します。他のイニシアチブとは異なり、JA-SIG の中央認証サービスはオープンソースであり、広く使用され、理解しやすく、プラットフォームに依存せず、プロキシ機能をサポートしています。Spring Security は CAS を完全にサポートし、Spring Security の単一アプリケーションデプロイから企業全体の CAS サーバーで保護された複数アプリケーションデプロイへの簡単な移行パスを提供します。
CAS の詳細については、https://www.apereo.org (英語) を参照してください。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 つのタイプがあります。サービスチケットを認証するもの、プロキシチケットを取得できるもの、プロキシチケットを認証するもの。プロキシチケットの認証は、プロキシのリストを検証する必要があり、多くの場合、プロキシチケットを再利用できるため、異なります。
Web ブラウザー、CAS サーバー、Spring セキュリティで保護されたサービス間の基本的な相互作用は次のとおりです。
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 (英語) の可能性があります。PasswordHandler
(または CAS 3.0 を使用する場合は AuthenticationHandler
)を使用して、ユーザー名とパスワードが有効かどうかを判断します。ticket
パラメーターも含まれます。これは、「サービスチケット」を表す不透明な文字列です。前の例を続けると、ブラウザーがリダイレクトされる URL は https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ (英語) になる可能性があります。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 (英語) も含まれる場合があります。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 にはいくつかの実装が用意されています: RejectProxyTickets
、AcceptAnyCasProxy
、NamedCasProxyDecider
。これらの名前は、信頼できるプロキシの List
を提供できる NamedCasProxyDecider
を除いて、ほとんど自明です。CasAuthenticationProvider
は、次に Assertion
に含まれるユーザーに適用される GrantedAuthority
オブジェクトをロードするために AuthenticationUserDetailsService
をリクエストします。CasAuthenticationProvider
は TicketResponse
および GrantedAuthority
に含まれる詳細を含む CasAuthenticationToken
を作成します。CasAuthenticationFilter
に戻り、作成された CasAuthenticationToken
がセキュリティコンテキストに配置されます。AuthenticationException
(または構成に応じてカスタム宛先)を引き起こした元のページにリダイレクトされます。まだここにいるのは良いことです! これがどのように構成されているか見てみましょう
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
を指すリンクをクリックするようユーザーに指示する必要があります。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 を使用して認証するステートレスサービスをセットアップする方法について説明します。
ステートレスサービスを認証するには、アプリケーションはプロキシ許可チケット(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>
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 で試す前に、これを機能させることが重要です
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 認証を簡単に使用できることを意味します。
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 で保護されたオブジェクトにアクセスできません。
AbstractSecurityInterceptor
は、セキュアオブジェクトコールバックフェーズ中に、SecurityContext
および SecurityContextHolder
の Authentication
オブジェクトを一時的に置き換えることができます。これは、元の Authentication
オブジェクトが AuthenticationManager
および AccessDecisionManager
によって正常に処理された場合にのみ発生します。RunAsManager
は、SecurityInterceptorCallback
の間に使用される置換 Authentication
オブジェクトがあれば、それを示します。
セキュアオブジェクトコールバックフェーズ中に Authentication
オブジェクトを一時的に置き換えることにより、セキュアな呼び出しは、異なる認証および認可資格情報を必要とする他のオブジェクトを呼び出すことができます。また、特定の GrantedAuthority
オブジェクトの内部セキュリティチェックを実行することもできます。Spring Security は SecurityContextHolder
のコンテンツに基づいてリモートプロトコルを自動的に構成する多くのヘルパークラスを提供するため、これらの run-as 置換はリモート Web サービスを呼び出すときに特に役立ちます
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_AS
ConfigAttribute
が続きます。例: 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
は、セキュリティ上の理由から作成後に不変です
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").permitAll()
); }
更新された構成は、ログインページの場所を指定します。 | |
ログインページへのアクセスをすべてのユーザー(認証されていないユーザー)に許可する必要があります。 |
現在の構成の JSP で実装されたログインページの例を以下に示します。
![]() | メモ |
---|---|
以下のログインページは、現在の構成を表しています。デフォルトの一部がニーズを満たさない場合、構成を簡単に更新できます。 |
<c:url value="/login" var="loginUrl"/> <form action="${loginUrl}" method="post"><c:if test="${param.error != null}">
<p> Invalid username and password. </p> </c:if> <c:if test="${param.logout != null}">
<p> You have been logged out. </p> </c:if> <p> <label for="username">Username</label> <input type="text" id="username" name="username"/>
</p> <p> <label for="password">Password</label> <input type="password" id="password" name="password"/>
</p> <input type="hidden"
name="${_csrf.parameterName}" value="${_csrf.token}"/> <button type="submit" class="btn">Log in</button> </form>
| |
クエリパラメーター | |
クエリパラメーター | |
ユーザー名は、username という名前の HTTP パラメーターとして存在する必要があります | |
パスワードは、password という名前の HTTP パラメーターとして存在する必要があります | |
「CSRF トークンを含める」が必要です。詳細については、リファレンスのセクション 5.1.1: “ クロスサイトリクエストフォージェリ (CSRF)” セクションを参照してください。 |
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 ページに埋め込まれたログインフォームなどを使用する場合は、この構成でフォームログインを引き続き使用できます。
基本認証とダイジェスト認証は、Web アプリケーションで一般的な代替認証メカニズムです。基本認証は、多くの場合、各リクエストで資格情報を渡すステートレスクライアントで使用されます。ブラウザーベースのユーザーインターフェースと Web サービスの両方でアプリケーションが使用されるフォームベース認証と組み合わせて使用することは非常に一般的です。ただし、基本認証はパスワードをプレーンテキストとして送信するため、HTTPS などの暗号化されたトランスポート層でのみ実際に使用する必要があります。
BasicAuthenticationFilter
は、HTTP ヘッダーで提示される基本認証資格情報の処理を担当します。これは、Spring リモーティングプロトコル(Hessian や Burlap など)、および通常のブラウザーユーザーエージェント(Firefox や Internet Explorer など)による呼び出しの認証に使用できます。HTTP 基本認証を管理する標準は RFC 1945、セクション 11 で定義されており、BasicAuthenticationFilter
はこの RFC に準拠しています。基本認証は、ユーザーエージェントに非常に広くデプロイされ、実装が非常に簡単であるため(HTTP ヘッダーで指定されたユーザー名: パスワードの Base64 エンコードであるため)、認証への魅力的なアプローチです。
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
が呼び出された場合のみです。
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 プロトコルの必須要件です。
![]() | メモ |
---|---|
ダイジェストは安全とは見なされないため、最新のアプリケーションではダイジェストを使用しないでください。最も明白な問題は、パスワードをプレーンテキスト、暗号化、または 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 で確実に機能し、ノンスタイムアウトなどを正しく処理します。
理論を確認したため、それを使用する方法を見てみましょう。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 の最小標準に準拠しています。
WebSecurityConfigurerAdapter (Javadoc)
を使用すると、ログアウト機能が自動的に適用されます。デフォルトでは、URL /logout
にアクセスすると、次の方法でユーザーがログアウトされます。
SecurityContextHolder
のクリア /login?logout
にリダイレクト ただし、ログイン機能の構成と同様に、ログアウト要件をさらにカスタマイズするためのさまざまなオプションもあります。
protected void configure(HttpSecurity http) throws Exception { http .logout(logout ->logout .logoutUrl("/my/logout")
.logoutSuccessUrl("/my/index")
.logoutSuccessHandler(logoutSuccessHandler)
.invalidateHttpSession(true)
.addLogoutHandler(logoutHandler)
.deleteCookies(cookieNamesToClear)
) ... }
ログアウトのサポートを提供します。 | |
ログアウトをトリガーする URL(デフォルトは | |
ログアウト後にリダイレクトする URL。デフォルトは | |
カスタム | |
ログアウト時に | |
| |
ログアウトの成功時に削除する Cookie の名前を指定できます。これは、 |
![]() | メモ |
---|---|
=== もちろん、XML 名前空間表記を使用してログアウトを構成することもできます。詳細については、Spring Security XML 名前空間セクションのログアウト要素のドキュメントを参照してください。=== |
一般に、ログアウト機能をカスタマイズするために、LogoutHandler (Javadoc)
および / または LogoutSuccessHandler (Javadoc)
実装を追加できます。多くの一般的なシナリオでは、これらのハンドラーは、Fluent API を使用するときに隠れて適用されます。
logout
要素は、特定の URL にナビゲートすることでログアウトのサポートを追加します。デフォルトのログアウト URL は /logout
ですが、logout-url
属性を使用して他の URL に設定できます。他の利用可能な属性の詳細については、ネームスペースの付録を参照してください。
一般的に、LogoutHandler (Javadoc)
実装は、ログアウト処理に参加できるクラスを示します。これらは、必要なクリーンアップを実行するために呼び出されることが期待されています。そのため、例外をスローすべきではありません。さまざまな実装が提供されます。
詳細については、セクション 10.13.4: “Remember-Me インターフェースと実装 ” を参照してください。
LogoutHandler
実装を直接提供する代わりに、流れるような API は、それぞれの LogoutHandler
実装をカバーするショートカットを提供します。例: deleteCookies()
では、ログアウト成功時に削除される 1 つ以上の Cookie の名前を指定できます。これは、CookieClearingLogoutHandler
の追加と比較したショートカットです。
LogoutSuccessHandler
は、LogoutFilter
によるログアウトの成功後に呼び出され、たとえば適切な宛先へのリダイレクトまたは転送。インターフェースは LogoutHandler
とほぼ同じですが、例外が発生する可能性があることに注意してください。
次の実装が提供されます。
上記のように、SimpleUrlLogoutSuccessHandler
を直接指定する必要はありません。代わりに、流れるような API は logoutSuccessUrl()
を設定することによりショートカットを提供します。これにより、SimpleUrlLogoutSuccessHandler
がカバーにセットアップされます。指定された URL は、ログアウトが発生した後にリダイレクトされます。デフォルトは /login?logout
です。
HttpStatusReturningLogoutSuccessHandler
は、REST API タイプのシナリオで興味深い場合があります。ログアウトが成功したときに URL にリダイレクトする代わりに、この LogoutSuccessHandler
では、返されるプレーン HTTP ステータスコードを提供できます。設定されていない場合、デフォルトでステータスコード 200 が返されます。
フォームログイン、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
を使用するのが最善です。
[7] セクション 10.15: “ 匿名認証 ” の章を参照
[8] 複数の <http>
要素の使用は重要な機能であり、たとえば、ネームスペースが同じアプリケーション内でステートフルパスとステートレスパスの両方を同時にサポートできるようにします。intercept-url
要素で属性 filters="none"
を使用する以前の構文は、この変更と互換性がなく、3.1 ではサポートされなくなりました。
[9]DigestAuthenticationFilter.passwordAlreadyEncoded
が true
に設定されている場合、HEX(MD5(username:realm:password))の形式でパスワードをエンコードすることができます。ただし、他のパスワードエンコードはダイジェスト認証では機能しません。