1. Spring Securityが解決する課題とは?(「なぜ存在するのか」から始めよう)
どのWebシステムでも直面する共通の課題があります。
- ユーザーはどのようにログインするか?
- ログイン状態はどのように保持するか? li>各リクエストがログイン済みかどうかをどのように判断するか?
- 異なるユーザーが異なるインターフェースにアクセスする場合、どのように権限を制御するか?
- ログイン失敗や無権限の場合、どのように統一されたレスポンスを返すか?
これらをすべて自前で実装しようとすると、以下のようなコードが大量に生まれます。
- インターセプター
- if-else文の羅列
- セッションの判定ロジック
- 繰り返し発生する権限チェックコード
Spring Securityの目標:これらの「セキュリティ問題」をビジネスロジックから完全に分離する
1.1. Spring Securityの公式な位置づけ
Spring Securityは、Springエコシステムにおけるセキュリティフレームワークであり、アプリケーションに対して以下を提供します。
- 認証(Authentication):ユーザーが誰であるかを確認するプロセス
- 認可(Authorization):認証されたユーザーが何をできるかを判断するプロセス
- リクエストレベルのセキュリティ保護:フィルターチェーンに基づく実装
1.2. Spring Securityを一言で理解する
Spring Security =
「フィルターチェーン」に基づいたセキュリティフレームワークであり、
リクエストがビジネスロジックに入る前に、
「誰であるか」と「何ができるか」を判断する役割を担います。
2. 全体のアーキテクチャ思想
2.1. 核となる設計思想
Spring Securityの設計は、4つの核心原則に従っています。
- ビジネスコードとの完全な分離
- Spring MVCとの分離
- すべてのセキュリティ制御をリクエストの入り口に集中させる
- 高い拡張性(特定の認証方式に縛られない)
これにより、以下の結論が導かれます。
👉 Spring Securityは、ControllerやInterceptorではなく、Filterである必要があります。
2.2. なぜFilterでなければならないのか?(Interceptorではない理由)
| 比較項目 | Filter | Interceptor |
|---|---|---|
| レベル | Servletコンテナレベル | Spring MVCレベル |
| カバー範囲 | すべてのリクエスト | Controllerのみをインターセプト |
| 実行順序 | 最も早い | Filterより遅い |
| セキュリティ | より高い | 迂回のリスクあり |
セキュリティは、できるだけ早い段階で処理されるべきです。これがFilterの重要性です。
3. Spring Securityの「骨格コンポーネント」
以下は、一連の実行フローの概要です。
| コンポーネント | 日本語名 | 本質的な役割 |
|---|---|---|
| SecurityFilterChain | セキュリティフィルターチェーン | すべてのセキュリティロジックの入り口 |
| Authentication | 認証情報 | 「誰であるか」の結果 |
| AuthenticationManager | 認証マネージャ | 認証の総合エントリーポイント |
| AuthenticationProvider | 認証プロバイダ | 実際の認証処理を実行 |
| UserDetailsService | ユーザー情報サービス | システムからユーザーを検索 |
| UserDetails | ユーザー詳細モデル | Securityが理解できるユーザーモデル |
| GrantedAuthority | 権限情報 | 権限ポイント |
| SecurityContext | セキュリティコンテキスト | ログイン状態を保持するコンテナ |
ここから、実際の実行順序に沿って説明します。
4. 認証(Authentication)——誰であるか?
4.1. 認証とは(ログインページではない)
認証 = 何らかの証明情報を使って、自分が誰であるかを証明すること
- ユーザー名 + パスワード
- Token / JWT
- サードパーティログイン情報
ログインは、認証の一つのUI的な表現に過ぎません。
4.2. なぜAuthenticationオブジェクトを单独で設計する必要があるのか?
Spring Securityは、統一された認証結果モデルを必要としています。どの方式(ユーザー名パスワード、JWT、OAuth2など)を使用しても、最終的に同じ構造に変換する必要があるからです。
これがAuthentication(認証情報)です。
4.3. Authentication(認証情報)の内部構造の詳細
`Authentication`は本質的にインターフェースであり、核心となるフィールドは以下の通りです。
| フィールド | 意味 |
|---|---|
| principal | ユーザー主体(通常はUserDetails) |
| credentials | 証明情報(例:パスワード。認証後はクリアされる) |
| authorities | 権限リスト |
| authenticated | 認証が成功したかどうか |
**ログイン済みかどうかの判断は、セッションではなく、`authenticated`フラグの真偽値で行われます。**
4.4. 認証フローの概要
- リクエストが到着
- Filterがリクエストをインターセプト
- 「未認証のAuthentication(認証情報)」を構築
- AuthenticationManager(認証マネージャ)に渡す
- 適切なAuthenticationProvider(認証プロバイダ)を見つける
- 検証が成功
- 「認証済みのAuthentication(認証情報)」を返却
- SecurityContext(セキュリティコンテキスト)に保存
5. AuthenticationManager & AuthenticationProvider
5.1. なぜAuthenticationManager(認証マネージャ)が必要なのか?
それは実務を行うのではなく、「ディスパッチセンター」の役割を担います。
理由は一つだけです。
👉 Spring Securityは、いかなる認証方式にも縛られないためです。
- ユーザー名パスワード?
- JWT?
- LDAP?
- OAuth2?
その役割は、「認証リクエストを"適切な人"に渡すこと」だけです。
5.2. AuthenticationProvider(認証プロバイダ):実際の作業を行う人
一つのProvider = 一つの認証方式
例えば:
| Provider | 役割 |
|---|---|
| DaoAuthenticationProvider | ユーザー名パスワード認証 |
| JwtAuthenticationProvider | JWT認証 |
| カスタムProvider | ビジネスロジックによる認証 |
6. UserDetailsService(ユーザー情報サービス) & UserDetails(ユーザー詳細モデル)
6.1. なぜSpring Securityは直接データベースを検索しないのか?
理由は以下の通りです。
- ユーザーテーブルの構造が分からない
- 権限モデルの設計方法が分からない
そこで、中間プロトコルを定義しています。
6.2. UserDetailsService(ユーザー情報サービス)
実装する必要があるインターフェースです。
UserDetails loadUserByAccount(String account);
役割は一つだけです。
「あなたのシステム内のユーザー」を、「Spring Securityが理解できるユーザー」に変換する
6.3. UserDetails(ユーザー詳細モデル)
これはSpring Securityがユーザーを統一して理解するための方法であり、以下を含みます。
- ユーザー名
- パスワード
- 無効/ロック状態
- 権限リスト(GrantedAuthority)
あなたのユーザーテーブル ≠ UserDetails
7. 認可(Authorization)——何ができるか?
7.1. 認可の本質
「誰であるか」が確定した後、「このリソースにアクセスする資格があるか」を判断すること
7.2. GrantedAuthority(権限情報)
これはSpring Securityが唯一認識する権限モデルです。
- 一つの文字列
- 一つの権限ポイントを表す
例:
ROLE_ADMIN
user:add
order:query
ロールは、単なる権限の一種の約束事に過ぎません。
7.3. 認可はどこで発生するのか?
FilterSecurityInterceptor(セキュリティインターセプタ)
このコンポーネントは以下の処理を行います。
- 現在のリクエストURLを取得
- アクセスルールを読み込む
- SecurityContext(セキュリティコンテキスト)からユーザーの権限を取得
- 許可/拒否(403)を決定
8. SecurityContext(セキュリティコンテキスト)
8.1. SecurityContextとは?
現在のリクエスト/スレッドのセキュリティコンテキスト
中には一つのものしか保存しません。
Authentication(認証情報)
8.2. なぜセッションを直接チェックしないのか?
理由は以下の通りです。
- JWTはステートレスである
- マイクロサービス環境ではセッションが必ずしも存在しない
- SecurityContextはより汎用的
セッションは、SecurityContextの一つの保存方法に過ぎません。
8.3. 現在のユーザーを正しく取得する方法
SecurityContextHolder
.getContext()
.getAuthentication();
すべての認可、監査、権限判断は、この情報に基づいて行われます。
9. Filter Chain(フィルターチェーン)
9.1. 本質的な理解
Spring Security = 責任連鎖パターンに基づいたFilterのチェーン
9.2. 主要なFilter
| Filter | 日本語説明 |
|---|---|
| UsernamePasswordAuthenticationFilter | フォームログイン認証 |
| OncePerRequestFilter | リクエストごとに一度(JWTでよく使われる) |
| ExceptionTranslationFilter | 例外の翻訳 |
| FilterSecurityInterceptor | 最終的な認可 |
JWT検証Filterは、認可の前に実行される必要があります。
10. JWTとステートレス認証
10.1. なぜJWTが登場したのか?
理由は以下の通りです。
- セッションはマイクロサービスに不向き
- 分散環境でのセッションのコストが高い
10.2. JWT + Spring Securityの本質
JWTは「認証情報のキャリア」に過ぎない
本当に重要なのは以下の点です。
- JWTの検証
- Authenticationの構築
- SecurityContextへの書き込み
JWTは、Spring Securityのコアモデルを変えるものではありません。
11. 401 vs 403
| ステータスコード | 意味 |
|---|---|
| 401 | 未認証(ログインしていない) |
| 403 | 認証済みだが権限がない |
12. ロールベースのアクセス制御(RBAC)
12.1. RBACとは?
RBACの核心思想は以下の通りです。
権限をユーザーに直接割り当てるのではなく、ロールに割り当てる
関係モデルは以下の通りです。
ユーザー(User)
↓
ロール(Role)
↓
権限(Permission)
↓
リソース(URL / メソッド)
なぜ企業システムのほとんどがRBACを使用するのか?
| 問題 | ユーザー-権限直接リンク | RBAC |
|---|---|---|
| メンテナンスコスト | 非常に高い | 低い |
| 権限の再利用 | 不可 | 可能 |
| 拡張性 | 悪い | 良い |
| 企業向け | ✔ |
RBACの本質:権限のグループ化 + 疎結合
12.2. Spring Securityは「ネイティブに」RBACをサポートしていますか?
Spring Security自体はRBACを実装していません。`GrantedAuthority(権限情報)`に基づいた汎用的な認可モデルを提供するだけです。RBACは、「ロール → 権限 → GrantedAuthority(権限情報)」のマッピング方式によって実現されます。
12.3. RBACのSpring Securityにおけるマッピング関係
| RBACの概念 | Spring Securityの対応 |
|---|---|
| ユーザー | UserDetails |
| ロール | ROLE_xxx(GrantedAuthority) |
| 権限 | GrantedAuthority |
| セッションのセキュリティ状態 | Authentication |
Spring Securityは文字列の権限しか認識せず、「ロールテーブル」を直接認識しません。
12.4. RBACの完全な実装フロー
ステップ1:データベースモデル
user
role
permission
user_role
role_permission
ステップ2:UserDetailsServiceでロールと権限を読み込む
List<String> permissionList =
rolePermissionMapper.findPermissionsByUserId(userId);
List<GrantedAuthority> authorityList =
permissionList.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
ロールであれ権限であれ、最終的にGrantedAuthorityに変換する必要があります。
ステップ3:Authenticationに注入する
return new UsernamePasswordAuthenticationToken(
userDetails,
null,
authorityList
);
ステップ4:ロール/権限に基づいたアクセス制御
http.authorizeHttpRequests()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/order/**").hasAuthority("order:query")
.anyRequest().authenticated();
13. メソッドレベルの権限制御(Method Security)
13.1. なぜURLレベルの権限だけでは不十分なのか?
URLレベルの権限では、以下の問題を解決できません。
- Serviceレイヤーの呼び出し
- 同じインターフェースでも、異なるパラメータで異なる権限
- 内部メソッドの保護
これがメソッドレベルの権限が存在する意味です。
13.2. Spring Securityが提供するメソッドレベルの権限アノテーション
| アノテーション | 説明 |
|---|---|
| `@PreAuthorize` | メソッド実行前の検証(最もよく使われる) |
| `@PostAuthorize` | メソッド実行後の検証 |
| `@Secured` | ロールに基づく |
| `@RolesAllowed` | JSR-250標準 |
13.3. @PreAuthorizeの底層原理
@PreAuthorize("hasAuthority('user:add')")
public void addUser() {}
実行フロー:
- メソッドが呼び出される
- Spring AOPがインターセプト
- SecurityContextからAuthenticationを取得
- Spring式を実行
- 権限を判断
- 許可するか決定
本質:AOP + SecurityContext + Spring式
14. 動的権限制御
14.1. 動的権限とは?
権限ルールをコードにハードコードせず、データベースから取得する
例:
/admin/** → ROLE_ADMIN
/user/** → user:query
/order/** → order:query
14.2. なぜ企業は動的権限を必ず実装するのか?
- 権限は頻繁に変更される
- コードを毎回変更したくない
- バックエンドで設定可能
- グレードリリース
14.3. Spring Securityで動的権限を実装する方法
核心思想は一言で言えば:
リクエストが来たときに、「現在のリクエストに何の権限が必要か」を動的に計算する
実装方法:
- カスタム`AuthorizationManager`
- またはカスタム`AccessDecisionManager`
- URL → 権限のマッピングはデータベースから取得
Spring Securityは「判断」を担当し、「ルールのソース」は担当しません。
15. パスワードセキュリティ機構(PasswordEncoder)
15.1. なぜ平文でパスワードを保存してはいけないのか?
- データベース漏洩のリスク
- 内部者のリスク
- コンプライアンスの問題
15.2. PasswordEncoderの役割
「暗号化 + パスワード検証」を担当
よく使われる実装:
| 実装 | 説明 |
|---|---|
| BCryptPasswordEncoder | 推奨(デフォルト) |
| NoOpPasswordEncoder | 平文(テスト用のみ) |
15.3. BCryptの核心的な利点
- ランダムなソルトを内蔵
- 同じパスワードでも、複数回暗号化すると結果が異なる
- レインボーテーブル攻撃を防ぐ
レインボーテーブル攻撃 = 攻撃者が事前に「一般的な平文パスワード → ハッシュ値」を計算してテーブルに保存しておき、データベースからハッシュ値を入手した際に、テーブルを直接参照して元のパスワードを逆算する攻撃。
16. CSRF、CORS
16.1. CSRF(クロスサイトリクエストフォージェリ)
CSRFの本質
ブラウザがCookieを自動的に送信する特性を利用した攻撃
Spring Securityの対策
- デフォルトでCSRF保護を有効化
- Tokenに基づく検証
JWTプロジェクトでは通常、CSRFを無効化します。
16.2. CORS(クロスオリジンリソースシェアリング)
Spring Securityでは:
- CORSはWebセキュリティの一部
- 明示的に許可する必要がある
http.cors().and().csrf().disable();
17. まとめ
Spring Securityの本質は以下の通りです。
- フィルターチェーンを使用し、リクエストがビジネスロジックに入る前に認証を完了させる。
- 認証結果を`Authentication(認証情報)`として統一し、`SecurityContext(セキュリティコンテキスト)`に保存する。
- リソースにアクセスする際に、`GrantedAuthority(権限情報)`に基づいて認可を判断する。