この記事では、Spring セキュリティについて調査し、OAuth 2.0 を使用した認証システムを構築します。
Spring Security は、Java ベースのアプリケーションに堅牢な認証およびアクセス制御メカニズムを実装するための、強力で高度にカスタマイズ可能なフレームワークです。これは Spring エコシステムの中核コンポーネントであり、Web アプリケーション、REST API、およびその他のバックエンド サービスを保護するために広く使用されています。 Spring Security を使用すると、アプリケーションで安全なプラクティスを構築および適用するための強固な基盤が得られます。
Spring Security の仕組み
Spring Security の動作方法について詳しく説明する前に、Java ベースの Web サーバーの リクエスト処理ライフサイクル を理解することが重要です。 Spring Security はこのライフサイクルにシームレスに統合され、受信リクエストを保護します。
Spring Security によるリクエスト処理ライフサイクル
Spring Security を使用して Spring ベースのアプリケーションで HTTP リクエストを処理するライフサイクルには、いくつかの段階が含まれており、それぞれがリクエストの処理、検証、セキュリティ保護において重要な役割を果たします。
1. クライアントリクエスト
クライアント (ブラウザ、モバイル アプリ、Postman などの API ツールなど) が HTTP リクエストをサーバーに送信すると、ライフサイクルが始まります。
例:
GET /api/admin/dashboard HTTP/1.1
2. サーブレットコンテナ
サーブレット コンテナ (例: Tomcat) はリクエストを受信し、それを Spring アプリケーションのフロント コントローラーである DispatcherServlet に委任します。ここからアプリケーションの処理パイプラインが開始されます。
3. Spring セキュリティ フィルター チェーン
DispatcherServlet がリクエストを処理する前に、Spring Security のフィルター チェーン がリクエストをインターセプトします。フィルター チェーンは一連のフィルターであり、それぞれが特定のセキュリティ タスクの処理を担当します。これらのフィルターは、リクエストがアプリケーション ロジックに到達する前に認証および認可の要件を満たしていることを確認します。
チェーン内のキーフィルター:
認証フィルター:
これらのフィルターは、ユーザー名/パスワード、JWT、セッション Cookie などの有効な認証情報がリクエストに含まれているかどうかを検証します。認可フィルター:
認証後、これらのフィルターは、認証されたユーザーが、要求されたリソースにアクセスするために必要なロールまたは権限を持っていることを確認します。その他のフィルター:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
4. セキュリティコンテキスト
認証が成功すると、Spring Security は Authentication オブジェクトを作成し、それを SecurityContext に保存します。このオブジェクトはスレッドローカル ストレージに保存されることが多く、リクエストのライフサイクル全体を通じてアクセスできます。
認証オブジェクト:
プリンシパル: 認証されたユーザー (ユーザー名など) を表します。
資格情報: JWT トークンやパスワードなどの認証の詳細が含まれます。
権限: ユーザーに割り当てられたロールと権限が含まれます。
フィルター チェーン内のフローの例:
リクエストは認証フィルターを通過します。
資格情報が有効な場合、Authentication オブジェクトが作成され、SecurityContext に追加されます。
認証情報が無効な場合、ExceptionTranslationFilter はクライアントに 401 Unauthorized 応答を送信します。
5. DispatcherServlet
リクエストが Spring Security フィルター チェーンを正常に通過すると、DispatcherServlet が引き継ぎます。
ハンドラーマッピング:
URL と HTTP メソッドに基づいて、受信リクエストを適切なコントローラー メソッドにマッピングします。コントローラー呼び出し:
マップされたコントローラーはリクエストを処理し、適切なレスポンスを返します。多くの場合、サービスやリポジトリなどの他の Spring コンポーネントの助けを借ります。
Spring Security がライフサイクルにどのように適合するか
Spring Security はフィルターを通じてこのライフサイクルに統合され、初期段階でリクエストをインターセプトします。リクエストがアプリケーション ロジックに到達するまでに、リクエストはすでに認証および許可されており、正当なトラフィックのみがコア アプリケーションによって処理されることが保証されます。
Spring Security の設計では、認証、認可、その他のセキュリティ対策が宣言的に処理されることを保証し、開発者が必要に応じて動作をカスタマイズまたは拡張できる柔軟性を提供します。これは、ベスト プラクティスを強制するだけでなく、最新のアプリケーションにおける複雑なセキュリティ要件の実装を簡素化します。
Spring Security コンポーネント: フィルター チェーンを超えて
Spring Security の フィルター チェーン について説明しました。次に、認証と認可のプロセスで重要な役割を果たす他の重要なコンポーネントについて詳しく説明します。
認証マネージャー
AuthenticationManager は、ユーザーの資格情報を検証し、それらが有効かどうかを判断するために使用される単一のメソッド、authenticate(Authentication 認証) を定義するインターフェースです。 AuthenticationManager は、複数のプロバイダを登録できるコーディネーターと考えることができ、リクエストの種類に基づいて、認証リクエストを正しいプロバイダに配信します。
認証プロバイダー
AuthenticationProvider は、資格情報に基づいてユーザーを認証するためのコントラクトを定義するインターフェイスです。これは、ユーザー名/パスワード、OAuth、LDAP などの特定の認証メカニズムを表します。複数の AuthenticationProvider 実装を共存させることができるため、アプリケーションはさまざまな認証戦略をサポートできます。
コアコンセプト:
認証オブジェクト:
AuthenticationProvider は、ユーザーの資格情報 (ユーザー名やパスワードなど) をカプセル化する Authentication オブジェクトを処理します。認証メソッド:
各 AuthenticationProvider は、実際の認証ロジックが存在する、authenticate(Authentication 認証) メソッドを実装します。このメソッド:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
- メソッドをサポートします: support(Class>authentication) メソッドは、AuthenticationProvider が指定されたタイプの認証を処理できるかどうかを示します。これにより、Spring Security は特定の認証リクエストを処理するための正しいプロバイダーを決定できるようになります。
例:
データベースを利用した AuthenticationProvider がユーザー名とパスワードを検証します。
OAuth ベースの AuthenticationProvider は、外部 ID プロバイダーによって発行されたトークンを検証します。
ユーザー詳細サービス
UserDetailsService は、Spring ドキュメントでユーザー固有のデータをロードするコア インターフェイスとして説明されています。これには、パラメーターとしてユーザー名を受け取り、 ==User== アイデンティティ オブジェクトを返す単一のメソッド loadUserByUsername が含まれています。基本的には、loadUserByUsername メソッドをオーバーライドする UserDetailsService のクラスを作成して実装します。
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
これら 3 つがどのように連携するかというと、AuthenticationManager は AuthenticationProvider に、指定された Provider の種類に従って認証を実行するよう依頼し、UserDetailsService 実装は AuthenticationProvider が userdetails を証明するのを支援します。
設定などに進む前に、JWT ベースの認証のための Spring Security の簡潔なフローを次に示します。
1. ユーザーリクエスト
ユーザーは、認証情報 (ユーザー名とパスワード) または JWT トークン (ヘッダー内) を使用して認証されたエンドポイントにリクエストを送信し、リクエストは認証フィルターに渡されます
-
AuthenticationFilter (例: UsernamePasswordAuthenticationFilter):
- 送信された資格情報 (通常はユーザー名とパスワードの形式) に基づいてユーザー認証を処理します。ここで UsernamePasswordAuthenticationFilter が活躍します。
- リクエストをリッスンし、ユーザー名とパスワードを抽出して、AuthenticationManager に渡します。
- しかし、ユーザー名とパスワードは渡しません。トークンだけを渡します。そのため、この AuthenticationFilter の前に、ユーザーが認証されており、ユーザー名とパスワードをチェックする必要がないことを認証プロセスに伝えるフィルターが必要です。これは、JWTFilter を作成することで行われます。
2.JWTフィルター
このカスタム フィルターは OncePerRequestFilter を拡張し、 UsernamePasswordAuthenticationFilter の前に配置され、リクエストからトークンを抽出して検証します。
トークンが有効な場合、UsernamePasswordAuthenticationToken を作成し、そのトークンをセキュリティ コンテキストに設定します。これにより、リクエストが認証されたことを Spring セキュリティに伝え、このリクエストが UsernamePasswordAuthenticationFilter に渡されると、UsernamePasswordAuthenticationToken を持っているためそのまま渡されます
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
UserDetails クラスを使用してユーザー名とパスワードを認証した後、トークンの代わりにユーザー名とパスワードを渡した場合、この UsernamePasswordAuthenticationToken は AuthenticationManager と AuthenticationProvider を使用して生成されます。
3. 認証マネージャー
- AuthenticationManager: これは認証リクエストを受け取り、それを設定した適切な AuthenticationProvider に委任します。
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
4.認証プロバイダ
UserDetailsService: AuthenticationProvider は UserDetailsService を使用して、ユーザー名に基づいてユーザーの詳細を読み込みます。そして、これを UserDetailsService
の実装で提供します。
資格情報の検証: 提供されたパスワードとユーザーの詳細に保存されているパスワードを比較します (通常は PasswordEncoder を使用します)。
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
Spring セキュリティが何をすべきかを認識できるように、これらすべてのさまざまなフィルターと Bean を構成する必要があるため、すべての構成を指定する構成クラスを作成します。
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
これまで Spring Security を使用して認証を理解し、構成してきました。ここからはテストしてみます。
AuthController (ログインと登録を処理します) と ProductController (ダミーの保護されたコントローラー) の 2 つのコントローラーを備えたシンプルなアプリを作成します
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{ return config.getAuthenticationManager(); }
@Bean public AuthenticationProvider authenticationProvider(){ DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsServiceImpl); authenticationProvider.setPasswordEncoder(passwordEncoder); return authenticationProvider; }
これまで、登録、ログイン、検証を実装してきましたが、Login With Google/Github 機能も追加したい場合は、OAuth2.0 を使用して実行できます
OAuth 2.0
OAuth 2.0 は、ユーザーが他のプラットフォーム (Google Drive や Github など) に保存されているリソースへのアクセスをサードパーティのアプリケーションに、それらのプラットフォームの資格情報を共有せずに許可できるようにする認証用に作成されたプロトコルです。
主に、「Google でログイン」、「github でログイン」などのソーシャル ログインを有効にするために使用されます。
Google、Facebook、Github などのプラットフォームは、このソーシャル サインインまたはアクセスの承認のために OAuth 2.0 プロトコルを実装する承認サーバーを提供します。
OAuth 2.0 の主要な概念
リソース所有者
クライアント
認可サーバー
リソースサーバー
アクセストークン
スコープ
助成金
次に、各コンセプトを 1 つずつ見ていきます
リソース所有者
リソース所有者は、サードパーティ アプリケーション (あなたのアプリケーション) を承認したいユーザーです。
クライアント
これは、リソース サーバーからデータまたはリソースにアクセスしようとしている (サードパーティの) アプリケーションです。
リソースサーバー
これはユーザーのデータが保存されるサーバーであり、サードパーティのアプリケーションによってアクセスされます。
認可サーバー
リソース所有者を認証し、クライアント (Google アカウントなど) にアクセス トークンを発行するサーバー。
アクセストークン
認可サーバーによってクライアントに発行される資格情報。これにより、クライアントはユーザーに代わってリソース サーバーにアクセスできるようになります。通常、有効期間は短く、すぐに期限切れになるため、ユーザーが再度認証する必要がないように、このアクセス トークンを更新するためのリフレッシュ トークンも提供されます。
スコープ
ユーザーによって付与される特定の権限。クライアントがユーザーのデータに対してできることとできないことを定義します。たとえば、承認の場合は、プロフィール、名前などのユーザー情報のみが必要ですが、ファイルアクセスの場合は別のスコープが必要です。
助成金
クライアント アプリケーションが認可サーバーからアクセス トークンを取得できる方法を指します。許可は、クライアント アプリケーションがリソース所有者の保護されたデータへのアクセスを許可されるプロセスと条件を定義します。
クライアント シークレットやその他の認証情報をブラウザに公開する必要がないため、安全です
OAuth 2.0 によって提供される、主に使用される 2 つの許可タイプがあります
-
認可コードグラント
これは最も使用されているタイプの許可/メソッドであり、最も安全であり、サーバー側アプリケーション用です
この例では、クライアントからバックエンドに認可コードが与えられ、バックエンドはクライアントにアクセス トークンを与えます。
プロセス:
- クライアントはユーザーを認可サーバーにリダイレクトします。
- ユーザーはログインして同意します。
- 認可サーバーは認可コードを発行します。
- クライアントは、バックエンドと認証コードを交換してアクセス トークンを取得します。
-
暗黙的な許可
シングルページ アプリ (SPA) またはバックエンドのないアプリケーションによって使用されます。この場合、アクセス トークンはブラウザ自体で直接生成され、発行されます。
プロセス:
- クライアントはユーザーを認可サーバーにリダイレクトします。
- ユーザーはログインして同意します。
- 認可サーバーはアクセストークンを直接発行します。
完全に理解するために両方を個別に実装しますが、最初に必要となる認可コードグラントを実装します
-
認可サーバー
プラットフォーム (google や github など) のいずれかにすることも、KeyCloak を使用して独自のプラットフォームを作成することも、OAuth 2.0 標準に準拠した独自のプラットフォームを構築することもできます (これは次のブログで行うかも知れません?)
-
Spring Boot アプリケーション
これは、コード交換、検証、ユーザー詳細の保存、JWT トークンの割り当てなどのすべての操作を処理するメインのバックエンド アプリケーション/サービスになります
-
React アプリケーション (フロントエンド)
これは、認可のためにユーザーを認可サーバーにリダイレクトするクライアントになります。
つまり、私たちの実装でやることは、フロントエンド(web/app)がバックエンドエンドポイントへのリダイレクトURIを使用してユーザーをGoogleログインにリダイレクトすることです。これによりさらに制御が行われます。これについては後で説明し、redirect_urlとともに説明します。また、アプリのクライアント ID も渡します。これらはすべてクエリ パラメーターで送信されます。
いいえ、ユーザーが Google に正常にログインすると、認証サーバー (Google の) はリクエストをバックエンド エンドポイントにリダイレクトします。そこで私たちが行うことは、認証サーバーと認証コードを交換して、アクセス トークンとリフレッシュ トークンを取得することです。必要に応じて認証を処理し、最後に、Cookie を含む応答をフロントエンドに送り返し、ダッシュボードまたは保護されたページにリダイレクトします。
ここでコードを調べますが、OAuth クライアントの Google コンソール ダッシュボードの承認されたリダイレクト URL にバックエンド エンドポイントの URL を必ず追加してください。
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
これで、これで問題なく動作します。テスト用に、コンテキストを持ち、ログインと登録の機能を知っているだけの単純なフロントエンド アプリケーションを作成できます。
ここまで読んでいただきありがとうございました。何かご提案がございましたら、コメント欄に書き込んでください
以上がSpring Security と OAuth についての詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

この記事では、2025年の上位4つのJavaScriptフレームワーク(React、Angular、Vue、Svelte)を分析し、パフォーマンス、スケーラビリティ、将来の見通しを比較します。 強力なコミュニティと生態系のためにすべてが支配的なままですが、彼らの相対的なポップ

この記事では、カフェインとグアバキャッシュを使用してJavaでマルチレベルキャッシュを実装してアプリケーションのパフォーマンスを向上させています。セットアップ、統合、パフォーマンスの利点をカバーし、構成と立ち退きポリシー管理Best Pra

この記事では、リモートコードの実行を可能にする重大な欠陥であるSnakeyamlのCVE-2022-1471の脆弱性について説明します。 Snakeyaml 1.33以降のSpring Bootアプリケーションをアップグレードする方法は、このリスクを軽減する方法を詳述し、その依存関係のアップデートを強調しています

Javaのクラスロードには、ブートストラップ、拡張機能、およびアプリケーションクラスローダーを備えた階層システムを使用して、クラスの読み込み、リンク、および初期化が含まれます。親の委任モデルは、コアクラスが最初にロードされ、カスタムクラスのLOAに影響を与えることを保証します

node.js 20は、V8エンジンの改善、特により速いガベージコレクションとI/Oを介してパフォーマンスを大幅に向上させます。 新機能には、より良いWebセンブリのサポートと洗練されたデバッグツール、開発者の生産性とアプリケーション速度の向上が含まれます。

大規模な分析データセットのオープンテーブル形式であるIcebergは、データの湖のパフォーマンスとスケーラビリティを向上させます。 内部メタデータ管理を通じて、寄木細工/ORCの制限に対処し、効率的なスキーマの進化、タイムトラベル、同時wを可能にします

この記事では、Lambda式、Streams API、メソッド参照、およびオプションを使用して、機能プログラミングをJavaに統合することを調べます。 それは、簡潔さと不変性を通じてコードの読みやすさと保守性の改善などの利点を強調しています

この記事では、キュウリの手順間でデータを共有する方法、シナリオコンテキスト、グローバル変数、引数の合格、およびデータ構造を比較する方法を調べます。 簡潔なコンテキストの使用、記述など、保守性のためのベストプラクティスを強調しています


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

SAP NetWeaver Server Adapter for Eclipse
Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

Dreamweaver Mac版
ビジュアル Web 開発ツール

メモ帳++7.3.1
使いやすく無料のコードエディター

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター
