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

サーブレット API の統合

Servlet 2.5+ 統合

HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser() [Oracle] (英語) は、通常は現在のユーザー名である SecurityContextHolder.getContext().getAuthentication().getName() の結果を返します。これは、アプリケーションで現在のユーザー名を表示する場合に役立ちます。さらに、これが null であるかどうかのチェックを使用して、ユーザーが認証済みか匿名かを示すことができます。ユーザーが認証されているかどうかを知ることは、特定の UI 要素を表示するかどうかを判断できます(つまり、ユーザーが認証された場合にのみログアウトリンクを表示する必要があります)。

HttpServletRequest.getUserPrincipal()

HttpServletRequest.getUserPrincipal() [Oracle] (英語) は SecurityContextHolder.getContext().getAuthentication() の結果を返します。これは、ユーザー名とパスワードベースの認証を使用する場合、通常 UsernamePasswordAuthenticationToken のインスタンスである Authentication であることを意味します。これは、ユーザーに関する追加情報が必要な場合に役立ちます。例: ユーザーの姓名を含むカスタム UserDetails を返すカスタム UserDetailsService を作成した可能性があります。次の方法でこの情報を取得できます。

  • Java

  • Kotlin

Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName

通常、アプリケーション全体でこれほど多くのロジックを実行するのは悪い習慣です。代わりに、Spring Security とサーブレット API のカップリングを減らすために一元化する必要があります。

HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String) [Oracle] (英語) は、SecurityContextHolder.getContext().getAuthentication().getAuthorities() に、isUserInRole(String) に渡されたロールを持つ GrantedAuthority が含まれているかどうかを判別します。通常、このメソッドは自動的に追加されるため、ユーザーはこのメソッドに "ROLE_" プレフィックスを渡さないでください。例: 現在のユーザーが権限 "ROLE_ADMIN" を持っているかどうかを確認したい場合は、次を使用できます。

  • Java

  • Kotlin

boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")

これは、特定の UI コンポーネントを表示する必要があるかどうかを判断できます。例: 現在のユーザーが管理者である場合にのみ、管理者リンクを表示できます。

Servlet 3+ 統合

次のセクションでは、Spring Security が統合される Servlet 3 メソッドについて説明します。

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) [Oracle] (英語) メソッドを使用して、ユーザーが認証されていることを確認できます。認証されていない場合、構成された AuthenticationEntryPoint を使用して、ユーザーに認証をリクエストします(つまり、ログインページにリダイレクトします)。

HttpServletRequest.login(String,String)

HttpServletRequest.login(String,String) [Oracle] (英語) メソッドを使用して、現在の AuthenticationManager でユーザーを認証できます。例: 以下は、ユーザー名 "user" とパスワード "password" で認証を試みます。

  • Java

  • Kotlin

try {
httpServletRequest.login("user","password");
} catch(ServletException ex) {
// fail to authenticate
}
try {
    httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
    // fail to authenticate
}

Spring Security に失敗した認証試行を処理させたい場合は、ServletException をキャッチする必要はありません。

HttpServletRequest.logout()

HttpServletRequest.logout() [Oracle] (英語) メソッドを使用して、現在のユーザーをログアウトできます。

通常、これは、SecurityContextHolder がクリアされ、HttpSession が無効化され、「自分を記憶」認証がクリーンアップされることを意味します。ただし、構成された LogoutHandler 実装は、Spring Security 構成によって異なります。HttpServletRequest.logout() が呼び出された後も、レスポンスの書き込みを担当していることに注意することが重要です。通常、これにはようこそページへのリダイレクトが含まれます。

AsyncContext.start(Runnable)

資格情報を新しいスレッドに確実に伝達する AsyncContext.start(Runnable) [Oracle] (英語) メソッド。Spring Security の同時実行サポートを使用して、Spring Security は AsyncContext.start(Runnable)をオーバーライドして、Runnable の処理時に現在の SecurityContext が使用されるようにします。例: 以下は、現在のユーザーの認証を出力します:

  • Java

  • Kotlin

final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
	public void run() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		try {
			final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
			asyncResponse.setStatus(HttpServletResponse.SC_OK);
			asyncResponse.getWriter().write(String.valueOf(authentication));
			async.complete();
		} catch(Exception ex) {
			throw new RuntimeException(ex);
		}
	}
});
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
    val authentication: Authentication = SecurityContextHolder.getContext().authentication
    try {
        val asyncResponse = async.response as HttpServletResponse
        asyncResponse.status = HttpServletResponse.SC_OK
        asyncResponse.writer.write(String.valueOf(authentication))
        async.complete()
    } catch (ex: Exception) {
        throw RuntimeException(ex)
    }
}

非同期サーブレットのサポート

Java ベースの構成を使用している場合、準備は完了です。XML 構成を使用している場合、いくつかの更新が必要です。最初のステップは、以下に示すように、少なくとも 3.0 スキーマを使用するように web.xml を更新したことを確認することです。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

</web-app>

次に、springSecurityFilterChain が非同期リクエストを処理するために設定されていることを確認する必要があります。

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
	org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>

以上です! これで、Spring Security は、SecurityContext が非同期リクエストでも伝搬されるようにします。

それはどのように機能するのでしょうか? 本当に興味がない場合は、このセクションの残りの部分をスキップして構いません。それ以外の場合は参照してください。これのほとんどはサーブレット仕様に組み込まれていますが、Spring Security が非同期リクエストを適切に処理できるようにするために微調整が少しあります。Spring Security 3.2 より前は、HttpServletResponse がコミットされるとすぐに、SecurityContextHolder からの SecurityContext が自動的に保存されました。これにより、非同期環境で問題が発生する可能性があります。例: 以下を検討してください。

  • Java

  • Kotlin

httpServletRequest.startAsync();
new Thread("AsyncThread") {
	@Override
	public void run() {
		try {
			// Do work
			TimeUnit.SECONDS.sleep(1);

			// Write to and commit the httpServletResponse
			httpServletResponse.getOutputStream().flush();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}.start();
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
    override fun run() {
        try {
            // Do work
            TimeUnit.SECONDS.sleep(1)

            // Write to and commit the httpServletResponse
            httpServletResponse.outputStream.flush()
        } catch (ex: java.lang.Exception) {
            ex.printStackTrace()
        }
    }
}.start()

課題は、このスレッドが Spring Security に認識されていないため、SecurityContext がこのスレッドに伝播されないことです。これは、HttpServletResponse をコミットするときに SecurityContext がないことを意味します。Spring Security が HttpServletResponse のコミット時に SecurityContext を自動的に保存すると、ログインしているユーザーが失われます。

バージョン 3.2 以降、Spring Security は、HttpServletRequest.startAsync() が呼び出されるとすぐに HttpServletResponse のコミット時に SecurityContext を自動的に保存しないように十分にスマートになっています。

Servlet 3.1+ 統合

次のセクションでは、Spring Security が統合される Servlet 3.1 メソッドについて説明します。

HttpServletRequest#changeSessionId()

HttpServletRequest.changeSessionId() (Javadoc) は、Servlet 3.1 以降でのセッション固定攻撃から保護するためのデフォルトの方法です。