最新の安定バージョンについては、Spring Security 6.4.2 を使用してください!

Remember-Me 認証

概要

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 + ":" + algorithmName + ":"
algorithmHex(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
algorithmName:     The algorithm used to generate and to verify the remember-me token signature

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

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

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

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

永久トークンアプローチ

このアプローチは、記事永続的ログイン Cookie のベストプラクティスの改善 (英語) をベースに若干の変更を加えたものです [ 1 ]。このアプローチを名前空間構成で使用するには、データソース参照を指定します。

<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 インターフェースと実装

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

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

デフォルトでは、この実装は MD5 アルゴリズムを使用してトークン署名をエンコードします。トークンの署名を検証するために、algorithmName から取得されたアルゴリズムが解析されて使用されます。algorithmName が存在しない場合は、デフォルトのマッチングアルゴリズムである MD5 が使用されます。署名エンコーディングと署名マッチングに異なるアルゴリズムを指定できます。これにより、ユーザーは、algorithmName が存在しない場合でも古いアルゴリズムを検証しながら、別のエンコーディングアルゴリズムに安全にアップグレードできます。これを行うには、カスタマイズした TokenBasedRememberMeServices を Bean として指定し、構成で使用できます。

  • Java

  • XML

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
	http
			.authorizeHttpRequests((authorize) -> authorize
					.anyRequest().authenticated()
			)
			.rememberMe((remember) -> remember
				.rememberMeServices(rememberMeServices)
			);
	return http.build();
}

@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
	RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
	TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
	rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
	return rememberMe;
}
<http>
  <remember-me services-ref="rememberMeServices"/>
</http>

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

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

  • Java

  • XML

@Bean
RememberMeAuthenticationFilter rememberMeFilter() {
    RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter();
    rememberMeFilter.setRememberMeServices(rememberMeServices());
    rememberMeFilter.setAuthenticationManager(theAuthenticationManager);
    return rememberMeFilter;
}

@Bean
TokenBasedRememberMeServices rememberMeServices() {
    TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices();
    rememberMeServices.setUserDetailsService(myUserDetailsService);
    rememberMeServices.setKey("springRocks");
    return rememberMeServices;
}

@Bean
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider();
    rememberMeAuthenticationProvider.setKey("springRocks");
    return rememberMeAuthenticationProvider;
}
<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 でさらに構成する必要があります。

  • テストのみを目的とした InMemoryTokenRepositoryImpl

  • トークンをデータベースに保存する JdbcTokenRepositoryImpl

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


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