Remember-Me 認証

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

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

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

このアプローチでは、ハッシュを使用して、有用な記憶戦略を実現します。本質的に、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);

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

Spring Security が 2 つの実装を提供することは以前に見ました。これらのそれぞれを順番に見ていきます。

TokenBasedRememberMeServices

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

デフォルトでは、この実装は SHA-256 アルゴリズムを使用してトークン署名をエンコードします。トークンの署名を検証するために、algorithmName から取得したアルゴリズムが解析されて使用されます。algorithmName が存在しない場合、デフォルトのマッチングアルゴリズムである SHA-256 が使用されます。署名エンコーディングと署名マッチングに異なるアルゴリズムを指定できます。これにより、ユーザーは、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 に含まれません。これについては、この記事のコメントセクションで議論されています。