永続的な認証
ユーザーが保護されたリソースを初めてリクエストすると、資格情報の入力を求められます。資格情報の入力を求める最も一般的な方法の 1 つは、ユーザーをログインページにリダイレクトすることです。保護されたリソースをリクエストする認証されていないユーザーの要約された HTTP 交換は、次のようになります。
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login
ユーザーはユーザー名とパスワードを送信します。
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
ユーザーを認証すると、セッション固定攻撃を防ぐために、ユーザーは新しいセッション ID に関連付けられます。
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
後続のリクエストには、セッションの残りの部分でユーザーを認証するために使用されるセッション Cookie が含まれます。
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
SecurityContextRepository
Spring Security では、将来のリクエストに対するユーザーの関連付けは SecurityContextRepository
(Javadoc) を使用して行われます。SecurityContextRepository
のデフォルトの実装は DelegatingSecurityContextRepository
(Javadoc) で、次のものに委譲します。
HttpSessionSecurityContextRepository
HttpSessionSecurityContextRepository
(Javadoc) は SecurityContext
を HttpSession
に関連付けます。ユーザーを後続のリクエストに別の方法で関連付けたい場合、またはまったく関連付けたくない場合は、HttpSessionSecurityContextRepository
を SecurityContextRepository
の別の実装に置き換えることができます。
NullSecurityContextRepository
SecurityContext
を HttpSession
に関連付けることが望ましくない場合(つまり、OAuth で認証する場合)、NullSecurityContextRepository
(Javadoc) は SecurityContextRepository
の実装であり、何もしません。
RequestAttributeSecurityContextRepository
RequestAttributeSecurityContextRepository
(Javadoc) は、SecurityContext
をリクエスト属性として保存して、SecurityContext
をクリアする可能性のあるディスパッチ型間で発生する単一のリクエストに対して SecurityContext
が確実に利用できるようにします。
例: クライアントがリクエストを行い、認証された後、エラーが発生したと仮定します。サーブレットコンテナーの実装に応じて、エラーは、確立された SecurityContext
がクリアされてから、エラーディスパッチが行われることを意味します。エラーディスパッチが行われると、SecurityContext
は確立されません。これは、SecurityContext
が何らかの形で永続化されない限り、エラーページが SecurityContext
を認証または現在のユーザーの表示に使用できないことを意味します。
Java
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<b:bean name="contextRepository"
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
DelegatingSecurityContextRepository
DelegatingSecurityContextRepository
(Javadoc) は SecurityContext
を複数の SecurityContextRepository
デリゲートに保存し、指定された順序で任意のデリゲートから取得できるようにします。
これに最も役立つ配置は、RequestAttributeSecurityContextRepository
と HttpSessionSecurityContextRepository
の両方を同時に使用できるようにする次の例で構成されます。
Java
Kotlin
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
securityContext {
securityContextRepository = DelegatingSecurityContextRepository(
RequestAttributeSecurityContextRepository(),
HttpSessionSecurityContextRepository()
)
}
}
return http.build()
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<bean name="contextRepository"
class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
<constructor-arg>
<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
</constructor-arg>
</bean>
Spring Security 6 では、上記の例がデフォルトの構成です。 |
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter
(Javadoc) は、SecurityContextRepository
を使用するリクエスト間で SecurityContext
を永続化するロールを果たします。
アプリケーションの残りの部分を実行する前に、SecurityContextPersistenceFilter
は SecurityContextRepository
から SecurityContext
をロードし、それを SecurityContextHolder
に設定します。
次に、アプリケーションが実行されます。
最後に、SecurityContext
が変更された場合は、SecurityContextRepository
を使用して SecurityContext
を保存します。これは、SecurityContextPersistenceFilter
を使用する場合、SecurityContextHolder
を設定するだけで、SecurityContext
が SecurityContextRepository
を使用して永続化されることを意味します。
場合によっては、SecurityContextPersistenceFilter
メソッドが完了する前に、レスポンスがコミットされ、クライアントに書き込まれます。例: リダイレクトがクライアントに送信された場合、レスポンスはすぐにクライアントに書き戻されます。つまり、セッション ID がすでに書き込まれたレスポンスに含まれることができないため、ステップ 3 で HttpSession
を確立することはできません。発生する可能性のある別の状況は、クライアントが正常に認証された場合、レスポンスが SecurityContextPersistenceFilter
が完了する前にコミットされ、クライアントが SecurityContextPersistenceFilter
が完了する前に 2 番目のリクエストを行うことです。2 番目のリクエストに誤った認証が存在する可能性があります。
これらの問題を回避するために、SecurityContextPersistenceFilter
は HttpServletRequest
と HttpServletResponse
の両方をラップして、SecurityContext
が変更されたかどうかを検出し、変更された場合は、レスポンスがコミットされる直前に SecurityContext
を保存します。
SecurityContextHolderFilter
SecurityContextHolderFilter
(Javadoc) は、SecurityContextRepository
を使用するリクエスト間で SecurityContext
をロードするロールを果たします。
アプリケーションの残りの部分を実行する前に、SecurityContextHolderFilter
は SecurityContextRepository
から SecurityContext
をロードし、それを SecurityContextHolder
に設定します。
次に、アプリケーションが実行されます。
SecurityContextPersistenceFilter
とは異なり、SecurityContextHolderFilter
は SecurityContext
のみをロードし、SecurityContext
は保存しません。つまり、SecurityContextHolderFilter
を使用する場合は、SecurityContext
を明示的に保存する必要があります。
Java
Kotlin
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
securityContext {
requireExplicitSave = true
}
}
return http.build()
}
<http security-context-explicit-save="true">
<!-- ... -->
</http>
構成を使用する際、SecurityContextHolder
を SecurityContext
で設定するコードは、リクエスト間で永続化する必要がある場合、SecurityContext
を SecurityContextRepository
に保存することも重要です。
例: 次のコード:
SecurityContextPersistenceFilter
で SecurityContextHolder
を設定する Java
Kotlin
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
次に置き換える必要があります
SecurityContextHolderFilter
で SecurityContextHolder
を設定する Java
Kotlin
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)