カジュアルな技術ノート

小難しい技術のお話をできるだけわかりやすく...

Spring Security をわかりやすく(認証全体像編・その1)

何回かに渡って Spring Security の認証を扱っていきたいと思います。
Spring Security を触ったことのある人にまず聞いてみたいのですが、認証の実装に対してどんな印象を持っていますか?

いままで何人かの人にこの質問をぶつけてみましたが、 とても直感的にはわかりにくい という声を聞いております(かくいう僕自身、何度か挫折しました。。)。

よくわからないクラスがたくさん登場したり、必要な実装自体が非常に部分的だったりするので、全体像がよく見えなくなりがちです。

少しずつ全体像が見えるように。。

今回は Spring Security 認証の中でも、

  • 結局ログインって何をやってるのか
  • 結局ログイン済みユーザーかどう判断しているのか

と行ったところをざっくりと説明していきます。

一般的なログイン認証の話

最初に一般的なログイン認証の話をします。

ログイン認証では、ユーザーがユーザー名とパスワードを入力してその組み合わせが適切であれば、ログインが成功。
ログイン成功ページに進むことができます。
f:id:shin-kinoshita:20181120233023p:plain

この時に セッション ID が発行されて、ブラウザのクッキーに保存します。
ログイン後にリクエストを送信するたびに、ユーザー名とパスワードを入力してもらい認証をするのは面倒なので、代わりにこのセッション ID でログイン済みかどうかを判断します。
f:id:shin-kinoshita:20181120232855p:plain

これが一般的なログイン認証の話ですが Spring Security でも同じ方法を取っています。
Spring Security を有効化したアプリケーションにアクセスして、何かしらの Web ページを開き、そこでのクッキーを確認してみましょう(ブラウザの拡張機能で簡単にみられます)。

すると JSESSIONID という名前でセッション ID が保存されているのが確認できるはずです。

セッションに保存されている Authentication

ここから Spring Security 独自の話にシフトしていきます。

一般的な話としてログイン時にセッション ID が発行されて、この情報を元にユーザーがログイン済みかどうかを判断するという話でした。
これを実現するためには、サーバーサイドでセッション ID に紐づいたログイン済みユーザーの情報を保持しておく必要があります。

このログイン済みユーザー情報を保持するのが Authentication インターフェースです。
実装を見ていきましょう。

public interface Authentication extends Principal, Serializable {
  // 1. 認可情報の getter
  Collection<? extends GrantedAuthority> getAuthorities();

  // 2. パスワードの getter
  Object getCredentials();

  Object getDetails();

  // 3. ユーザー名の getter
  Object getPrincipal();

  boolean isAuthenticated();

  void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

特に認証に大きく関わるメソッドにコメントをつけてみましたが、それぞれ

  1. 認可情報
  2. パスワード
  3. ユーザー名

を保持していると考えてください。

結局ログインで何を行なっているのか

結局ログインで行なっていることは、入力したユーザー名とパスワードの組み合わせが正しければ、それを元に Authentication オブジェクトを作成することです。
この Authentication は、セッション領域にログイン成功したユーザーの各 JSESSIONID ごとに作成されます。
f:id:shin-kinoshita:20181122212457p:plain

もちろんユーザー名とパスワードの組み合わせが間違っていると、Authentication はセッションに保存されず、ログインページにリダイレクトされます。

結局どうログイン済みか確認してるのか

ログイン成功時には Authentication オブジェクトが作成されると言うことでした。 その後、認証が必要なページにアクセスした時は JSESSIONID を元に作成された Authentication の取得を試みます。
この Authentication が見つかれば問題なくリクエストしたページにアクセスできるというわけです。
f:id:shin-kinoshita:20181121005317p:plain

*正確には認可情報を検証し、権限がない時はページに遷移できませんが、今回は本筋から外れるので割愛します。

実際に Authentication を取得してみよう!

ここまでセッション領域に保持する Authentication オブジェクトの話を進めてきましたが、少し概念的な話になってしまったので、実際に取得するコードを書きます。

HttpSession session = request.getSession();
SecurityContext securityContext = (SecurityContext)session.getAttribute("SPRING_SECURITY_CONTEXT");
Authentication authentication = securityContext.getAuthentication();

request はリクエスト情報を持った HttpServeltRequest 型のオブジェクトです(DI をするなどして受け取ってください)。
そして最終的に取得した authentication が Authentication オブジェクトです。
Authentication オブジェクトはセッションの SPRING_SECURITY_CONTEXT 属性値である SecurityContext 型のオブジェクトの中に保存されています。

SecurityContext オブジェクトは、ログイン済みかどうかに関わらず取得できますが、 Authentication オブジェクトは、ログイン済みでないと null が格納されます。

まとめ

今回は Spring Security の認証をざっくりと説明しました。
ログインは、Authentication オブジェクトの作成。
ログイン済みかどうかは、Authentication オブジェクトがセッションに保持されているかをチェックすることで確認していると覚えておいてください。

次回はもう少し正確に、たくさん登場するクラスたちが認証をどのような役割を担っているかをみていきます。