認証とかセッションとかCookieとかなんやらかんやら
はじめに
Spring Securityについて勉強しようと思い、公式サイトや有識者の方々のブログ記事やChatGPTに聞いてみたりといろいろしてたのですが、結局のところ私が知りたいのは「Spring Securityの中身の仕組み」ではなく、「実際それで作ったアプリはどう動いてるのか?」ということでした。
そこで、Spring Securityを使ってログイン機能を有する超簡単なWebアプリケーションを作って、それを実際に動かし、ブラウザでは何が起こっているのかを覗いてみました。
前提
前提 1. ソースコードは載せません
本記事は実装の仕方やソースコードについて解説をする記事ではありません。
あくまで本記事は、Spring Securityで実装をした場合の処理の動きをブラウザ側から覗き見ることが主旨です。
説明が必要な箇所では部分的にソースコードを抜粋して紹介しますが、具体的な実装方法などについては公式のガイドなどを参照しながらご自身で調べていただきますようお願いします。
前提 2. Spring Securityについて解説をしません
本記事は「Spring Securityとは?」のような解説記事ではありません。
具体的な仕様や実装方法についてはSpring Securityの公式ガイドや有識者の方の記事などを参照してください。
検証した環境や技術など
| 項目 | バージョンや名称等 |
|---|---|
| ホストマシン | Windows 11 Pro |
| 動作環境 | ローカル(Eclipse) |
| ブラウザ | Google Chrome バージョン150.0.7871.181 |
| フレームワーク | Spring Boot 4.0.7 |
| 認証認可 | Spring Security 7.0.6 |
| フロントエンド | Thymeleaf |
| プログラミング言語 | Java 21 |
| 認証方式 | メールアドレス+パスワード入力 |
| ユーザー管理 | 自己DB管理(PostgreSQL) |
| セッション管理 | HTTP Session |
| セッション識別 | Cookie |
今回は「Spring Security 7.0.6」を使用しますが、Spring Securityはバージョンによって実装方法がかなり異なる部分もあるので注意が必要です。
以下のリンクはその一例です。
Spring Blog - Spring Security without the WebSecurityConfigurerAdapter
作ったアプリの概要
前項の通り、開発に使った技術はSpring Bootと、認証認可機能としてSpring Securityを使用し、画面はテンプレートエンジンのThymeleafで、認証方式はメールアドレス+PW入力方式で、その認証情報やユーザーごとの認可情報は自己DB管理でPostgreSQLに保存しています。
アプリの内容自体は本当にチープで簡単なものです![]()
- アクセスをするとログイン画面が表示
- ログインをするとトップ画面に遷移
- ログアウトをするとログイン画面に戻る
- (他にも管理者専用画面やユーザー新規登録機能などいろいろ実装しましたが、今回は触れません)
(簡単な内容とはいえ、Spring Securityを使ってメールアドレス+PW入力方式を自己DB管理するのはこんなに大変なのかと痛感させられました…
)
検証方法
ローカルのIDEのEclipseでSpring Bootアプリケーションを起動し、ブラウザのGoogle Chrome(以降、Chrome)でアクセスし、そのChromeの開発者ツール機能で通信の内容などを見るという内容です。
アプリ内操作の手順は以下の通りです。
- 検証1. アプリケーションにアクセス(http://localhost:8080/)
- 検証2. 事前に登録してたユーザー情報でログインをする
- 検証3. ログアウトをする
この3つだけです。
ブラウザのデベロッパーツールの見方
ブラウザのデベロッパーツールを見てみましょう。
Chromeの場合は「設定(3つの点) → その他のツール → デベロッパーツール」で開きます。
すると画面にいろいろと表示されました。
今回は主にこのデベロッパーツールの「Network」タブの中からどのような通信がされているのかを覗き見ます。
検証1. アプリケーションにアクセス
それではアプリケーションを起動して、http://localhost:8080/にアクセスしてみましょう。
すると簡単なログイン画面が表示され、デベロッパーツールの下のほうには新たな2行が出ました。
「localhost」と「login」とあります。
時系列として、このデベロッパーツールのNetworkタブですが、通信の内容が新しいものが下に追加されていくようになっています。
1-1. localhost:アプリケーションにアクセス
まずはこの「localhost」を押して見ます。
「GET 302 Found」とあります。
HTTPステータスコードの「302 Found」はリダイレクトを意味しており、下のほうにある「Location: http://localhost:8080/loginにリダイレクトして」という意味になります。
検証1-1のポイント - HTTPリクエスト制御
まだログインすらしてないのに色々と面白い部分があります。
まず最初に、私は「http://localhost:8080/」にアクセスしましたが、「http://localhost:8080/login」にリダイレクトされました。
私はこのようなルーティングをするためのControllerクラスなどは一切実装しておらず、Spring Securityが自動的に処理しています。
実際に実装したJavaクラスを見ていきましょう。
以下はSpring Securityの設定などをするSecurityConfig.javaです。
// 一部抜粋
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
"/login",
"/signup",
"/css/**",
"/js/**",
"/images/**")
.permitAll()
// ~省略~
.anyRequest()
.authenticated()
)
securityFilterChain()というメソッドですが、Spring Securityで実装をしたプロジェクトはControllerにたどり着く前に、必ずこのsecurityFilterChain()を通ってきます。
そしてここで設定してる内容としては「requestMatchers()で指定してるURL以外へのアクセスは認証済みユーザーしかアクセスできません」という意味です。
つまり私は最初に「/」にアクセスをしましたが、まだログインをしていない = 未認証のためアクセスできなかったのです。
そこで、未認証ユーザーからのアクセスを、Spring Securityの内部にある「ExceptionTranslationFilter」というのが「この人はログインしてないからログイン画面へ飛ばそう」っていうことで自動的に「/login」というパスを探してリダイレクトさせる処理が走ります。
そしてログイン画面の「http://localhost:8080/login」にたどり着いたというわけです。
この辺りの仕様は非常に複雑難解で膨大な処理が裏で自動で走っててすべてを理解するのは困難ですが、とても分かりやすくまとめてくれる有識者の方の記事があるのでぜひ参照してみてください。
つまりここで私は何が言いたいのかというと、先ほどお見せしたSecurityConfig.javaのsecurityFilterChain()にあの記述をするだけで、自分でログイン周りの実装を何もせずにこのようなHTTPリクエスト制御をSpring Securityが勝手にやってくれているということです。
1-2. login:ログイン画面に遷移(ログイン前)
引き続き「localhost」の「Cookies」のタブを開くと「JSESSIONID: 7D4644B330689BED5A84478AFEFCE982」とあります。
そのまま続けて下の行の「login」の中身を見てみます。
すると「http://localhost:8080/login」にリダイレクトされています。
「Cookies」のタブを開くと先ほど見た「JSESSIONID: 7D4644B330689BED5A84478AFEFCE982」が引き継がれています。
「Response」タブを開くと画面に表示されているHTMLでもあるレスポンスボディ部分が表示されます。
このHTMLをよく見ると、「<input type="hidden" name="_csrf" value="wK7..."/>」という箇所があります。
これはCSRF攻撃対策として、POSTメソッドのフォームの中に埋め込まれるトークンです。
検証1-2のポイント - ログイン前からセッションID発行&CSRFトークン発行
前項でログイン前 = 未認証だからログイン画面にリダイレクトされるのは分かりました。
しかし、まだログインしていないので認証情報などは何も持っていないはずなのに、セッションIDとかCSRFトークンとか発行されてます。
私は少し勘違いしていたのですが、そもそもセッションもCSRFトークンも、認証をするためだけに必要なものではありません。
ここで重要なのは「Login CSRF」です。
通常よくいわれる「CSRF攻撃」とは「ログイン済みのユーザー」が標的となったケースで語られることが多いと思うのですが、それに限らず、ログイン前であってもCSRF攻撃の被害に遭う可能性はあり、そのことを「Login CSRF」というそうです。
具体例としては、「強制ログイン」というものがあり、例えばAというサイトに攻撃者が登録していて、被害者にそのAサイトのリンクを送り付けて、被害者は知らず知らずのうちに攻撃者のアカウントで強制的にログインをさせられてて、そこで入力された個人情報などを盗まれたりする手口があるそうです。
つまり「ログイン済み」も「ログイン前」も、どちらも被害者のブラウザから意図しないHTTPリクエストの送信を防ぐという目的も込めて、このようなPOSTメソッドのログインフォームがあるような画面ではログイン前の段階からサーバは自動的にCookieへのセッション付与と、HTMLにCSRFトークンを埋め込んだ状態でレスポンスを返却します。
私は自分でこの「<input type="hidden" name="_csrf" value="wK7..."/>」という実装はしていませんが、Spring Securityが自動で付け加えてくれています。
検証2. 事前に登録してたユーザー情報でログインをする
それでは事前にDBに登録していたユーザー情報でログインをしてみましょう。
検証2-1. login:ログイン成功、そして再びリダイレクト
正しいメールアドレスとパスワードを入力すると無事にログインは成功するのですが、再び「302 Found」でリダイレクトされてて、そのリダイレクト先は「Location: http://localhost:8080/」となっています。
検証2-1のポイント・その1 - SavedRequestでログイン前のアクセスURLを保持
これは「ログインが成功したからトップ画面のhttp://localhost:8080/にリダイレクトさせてあげよう」ということではありません。
これもSpring Securityの機能で、セッションの中にある「SavedRequest」というものがログイン前にアクセスしていたURLの履歴を保持しており、この仕組みが働いて「ログインが成功したから元々アクセスしたかったURLに戻してあげよう」ということでhttp://localhost:8080/へのリダイレクトがされているということです。
なので、例えばログイン前にアクセスしていたのが「/admin」というページであれば、ログイン後には「/admin」に自動的にリダイレクトしてくれます。
参考
SavedRequest
そして「Cookies」タブを開くと「Request Cookies: JSESSIONID: 7D4...」と「Response Cookies: JSESSIONID: 783...」という2つのセッションIDがあります。
検証2-1のポイント・その2 - ログインが成功したら新たなセッションIDが発行される
ログイン前からすでにCookieにセッションが付与されていましたが、これは「Login CSRF」のためのCSRFトークンの発行及び検証のために発行されたものでした。
今回はログイン認証が成功したため、Spring Security内部では認証情報を保持する必要が出てきます。
そこで、それまでに使っていた古いセッションIDは破棄され、新たなセッションIDを発行し、それをCookieに付与して返却しています。
ここで注目すべきは、セッションID自体には認証情報を保持していないということです。
実際に認証認可の情報を保持しているのはSpring Scurity内部の「Security Context」であり、新たに発行したセッションIDと紐づけることで、「ログイン済みかどうか?」や「権限はどれぐらいあるのか?」などを判断しています。
ブラウザからはそこまで見れませんが、それは有効なセッションIDに紐づく情報を「Security Context」が裏でごにょごにょやってくれてるので安心安全に通信ができます。
検証2-2. localhost:トップ画面表示
やっとここでトップ画面の表示にまでたどり着きます。
「http://localhost:8080/ GET 200 OK」となりました。
「Cookies」タブを開くと、セッションIDがあり、「Request Cookies: JSESSIONID: 783...」となっており、先ほど上で説明した新たなセッションIDになっていることが分かります。
検証3. ログアウトをする
検証3-1. logout:ログアウト後もリダイレクト
ここは画面遷移をどうするかの設計次第なところでもありますが、今回は「ログアウトボタン押下」→「ログイン画面に戻る」という実装にしているので、再びログイン画面へリダイレクトとしています。
一応、ログアウトをしたことを明示的にするために「ログアウトしました」という文言を出したかったこともあり、リダイレクト先のURLは「/login?logout」としています。
検証3-1のポイント - ログアウト処理もSpring Securityがやってくれる
ログイン前の処理と同様、ログアウト時もこのSecurityConfig.javaのsecurityFilterChain()に記述するだけでいろいろと自動で処理をしてくれます。
// 一部抜粋
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ~省略~
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
-
.logoutUrl("/logout"):「/logout」にリクエストが来たらログアウト処理を実行してくださいという意味
検証3-2. login?logout:ログイン画面に戻る
さて、前項でログアウトしてからログイン画面にリダイレクトされてきたので、「/login?logout」というURLに「GET 200 OK」となっており、無事にログアウトが成功してログイン画面に戻ってきました。
そして「Cookies」タブを開くと、またまた2つのセッション情報が表示されていますね。
検証3-2のポイント - ログアウト成功時も新しいセッションIDが発行される
このセッションIDが新しく発行されてるの、どこかで見かけませんでしたか? そうです、ログイン成功時にセッションIDが新たに発行されてましたね。 それと同じことがログアウト成功時にも起こります。
なぜかというと、ログイン前の状態を思い出してもらえたら分かると思いますが、今回のように未認証の状態でPOSTメソッドのフォームがあるログイン画面にリダイレクトさせる場合、「Login CSRF」対策としてCSRFトークンの付与をするためのセッションID付与をしていました。
今回はログアウトをしたので、ログアウト後もずっと同じセッションIDが引き継がれていたらログアウトしたはずなのに再びログインができてしまうし、かと言ってセッションIDを完全に破棄してしまったらLogin CSRFトークンの検証などができなくなってしまいます。そのため、ログアウトをした後は新たなセッションIDを発行して自動でそれをCookieに付与するログイン前と同じ状態に戻すわけです。
まとめ
- アプリにアクセス
- 自動でログイン画面に遷移
- セッションID&CSRFトークン発行
- ログイン成功
- ログイン前にアクセスしてた画面に自動で遷移
- セッションIDを新たに発行
- ログアウト成功
- ログイン画面に自動で遷移
- セッションIDを新たに発行
おわりに
今回はめちゃくちゃ簡単でざっくりした内容ではありましたが、Spring Securityが実際何をしているのかをブラウザから覗き見ることで、実際に何が起こっているのかが体感しやすかったと思います。
Spring Securityで出来ることはまだまだ沢山あるので、今後はもう少し高度で実務的な内容にも挑戦してみたいと思います。
出典・参考
- Spring Security 7.0
- Spring Security の仕組みを整理してみた
- Spring Security の認証処理アーキテクチャを学ぶ
- Spring Security - Servlet Applications
- Github - spring-security - Java Configuration
- Spring Blog - Spring Security without the WebSecurityConfigurerAdapter
- Spring Boot - Spring Security
- TERASOLUNA - 9.5. CSRF対策
- クロスサイトリクエストフォージェリー (CSRF)
- 302 Found
- ログインフローの理解とCSRF対策
- BREACH攻撃














