Spring Security | 認証 | Authentication

AuthenticationはSpring Securityを構成する重要なクラスの一つです。ユーザー名や付与された権限など、認証済みユーザーの情報を保持します。

認証に成功すると、AuthenticationProviderは認証済みのAuthenticationオブジェクトを作成して返します。isAuthenticated()はtrueを返します。その後の認可チェックなどで、このオブジェクトを参照します。

Authenticationの取得と参照できる情報

SecurityContextHolderSampleServlet.java

package sample.spring.security.servlet;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static java.util.stream.Collectors.*;

@WebServlet("/authentication")
public class SecurityContextHolderSampleServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("[authorities]");
        System.out.println("  " + auth.getAuthorities().stream()
                                    .map(GrantedAuthority::getAuthority)
                                    .collect(joining("\n  ")));

        WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
        System.out.println("[details]");
        System.out.println("  IP Address : " + details.getRemoteAddress());
        System.out.println("  Session ID : " + details.getSessionId());

        UserDetails principal = (UserDetails) auth.getPrincipal();
        System.out.println("[principal]");
        System.out.println("  username : " + principal.getUsername());
        System.out.println("  password : " + principal.getPassword());

        System.out.println("[credentials]");
        System.out.println("  " + auth.getCredentials());
    }
}

実行結果

[authorities]
  USER
[details]
  IP Address : 0:0:0:0:0:0:0:1
  Session ID : 0225051BCDFE2C34D55DF4FA9D9685C2
[principal]
  username : hoge
  password : null
[credentials]
  null
  • 現在のリクエストに関連するAuthenticationは、SecurityContextHolder.getContext().getAuthentication()で取得します。
    • SecurityContextHolder.getContext()は、現在のリクエストに関連するSecurityContextを返します。
  • Authentication.getAuthorities()は、ログインユーザーに付与された権限を返します。
  • Authentication.getDetails()は、デフォルトでWebAuthenticationDetailsを返します。
    • IPアドレスとセッションIDを取得できます。
  • Authentication.getPrincipal()は、ログインユーザーのUserDetailsを返します。
  • Authentication.getCredentials()は認証に使用する情報、通常はログインパスワードを返します。

安全のため、ログイン成功後にAuthenticationManagerProviderManager)がパスワード情報をnullへ設定して明示的に削除します。eraseCredentialsAfterAuthenticationオプションで削除を無効にできます。

Authenticationの保存場所

認証後、Authenticationを保持するSecurityContextはデフォルトでHttpSessionへ保存されます。

同じセッションで再度アクセスすると、保存されたSecurityContextが取得され、SecurityContextHolderへ設定されます。

デフォルトでは、SecurityContextHolderThreadLocalを通じてSecurityContextを保存します。これにより、現在のリクエストに関連するコンテキストへグローバルにアクセスできます。

厳密には、SecurityContextHolderSecurityContextHolderStrategyのインスタンスを保持します。ThreadLocalSecurityContextHolderStrategySecurityContextThreadLocalへ保存します。

SecurityContextPersistenceFilterSecurityContextの読み書きとリクエスト単位のThreadLocalのクリアを行います。

ThreadLocal以外の保存方法

SecurityContextHolderStrategyには、ThreadLocalSecurityContextHolderStrategy以外にも2つの実装があります。

GlobalSecurityContextHolderStrategy

アプリケーション全体で1つのSecurityContextを保存します。

実行ユーザーが1人だけの可能性があるスタンドアロンアプリケーションなどで役立ちます。複数のスレッドを作成しても、すべてで同じ認証情報を共有できます。

InheritableThreadLocalSecurityContextHolderStrategy

新しいスレッドを作成した場合に、親スレッドのSecurityContextを共有します。

複数ユーザーが利用するアプリケーションで、ユーザーごとの処理から新しいスレッドを作成し、バックグラウンド処理を実行する場合に役立ちます。スレッドは分かれますが、認証情報は親スレッドから継承します。

保存方式は次のいずれかの方法で変更できます。

システムプロパティで指定する

システムプロパティspring.security.strategyをJVM起動オプションで渡します。

Tomcat起動時に指定する場合

$ set JAVA_OPTS=-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

$ cd %TOMCAT_HOME%\bin

$ startup.bat

staticメソッドで指定する

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

SecurityContextHolderにはsetStrategyName(String)が用意されています。引数にはSecurityContextHolderで定義された定数を指定します。