多要素認証の Spring OAuth2 実装の完全なコードは、このリンクのファイル共有サイトにアップロードされています。わずか数分で任意のコンピューターで現在の問題を再現するための手順を以下に示します。
**現在の問題:**
ほとんどの認証アルゴリズムは正しく機能します。プログラムは、以下に示す制御フローの最後までブレークしません。具体的には、「http://localhost:9999/uaa/oauth/token に無効な CSRF トークンが見つかりました」というエラーが、以下の **2 回目のパス**の最後にスローされています。上記のリンクのアプリは、このSpring Boot OAuth2 GitHub サンプルの「authserver」アプリにカスタムの「OAuth2RequestFactory」、「TwoFactorAuthenticationFilter」、および「TwoFactorAuthenticationController」を追加することによって開発されました。**この CSRF トークン エラーを解決して 2 要素認証を有効にするには、以下のコードに具体的にどのような変更を加える必要がありますか?** 私の調査によると、`CustomOAuth2RequestFactory` (このリンクの API) は、 `AuthorizationRequest`および`TokenRequest`を管理する方法を定義しているため、ソリューションを構成する場所になる可能性があります。**公式の OAuth2 仕様のこのセクションは、認可エンドポイントに対して行われるリクエストの `state` パラメータが `csrf` トークンが追加される場所であることを示しています。** また、リンク内のコードは Authorization Code Grantを使用しています。これは、フローのステップ C が「csrf」コードを更新しないため、ステップ D でエラーが発生することを意味します (ステップ C とステップ D を含むフロー全体は、公式仕様。)
**現在のエラーを取り巻く制御フロー:**
現在のエラーは、以下のフローチャートの `TwoFactorAuthenticationFilter` を介して **2 回目のパス** でスローされています。制御フローが **SECOND PASS** に入るまで、すべてが意図したとおりに機能します。次のフローチャートは、ダウンロード可能なアプリのコードで使用される 2 要素認証プロセスの制御フローを示しています。

**ログの内容:**
HTTP リクエスト ヘッダーとレスポンス ヘッダーは次のことを示しています。 sGXQ4v` の後に `GET 9999/secure/two_factor_authenticated` が続きます。1 つの XSRF トークンは、これらの取引所全体で一定のままです。2.) 正しい PIN コードを使用した `9999/secure/two_factor_authentication` への POST は、同じ `XSRF` トークンを送信し、`POST 9999/oauth/authorize` に正常にリダイレクトされ、`TwoFactorAuthenticationFilter.doFilterInternal( )` に進み、`request 9999/oauth/token` に進みますが、同じ古い XSRF トークンが新しい `XSRF` トークン値と一致しないため、`9999/oauth/token` は要求を拒否します。ファーストパス**。公式スペック。しかし、これが問題を引き起こしているかどうかは明らかではありません。また、`TwoFactorAuthenticationController.POST` から完全に形成されたリクエストを送信するためにパラメータにアクセスする方法も明確ではありません。「POST 9999/secure/two_factor_authentication」コントローラー メソッドの「HttpServletRequest」で「パラメーター」「Map」の SYSO を実行しましたが、含まれているのは「pinVal」変数と「_csrf」変数だけです。このリンクをクリックすると、ファイル共有サイトですべての HTTP ヘッダーと Spring Boot ログを読み取ることができます。
**失敗したアプローチ:**
Spring Security 3.2環境で同様の問題に対する@RobWinchのアプローチを 試しました、しかし、このアプローチはSpring OAuth2のコンテキストには適用されないようです. 具体的には、以下に示す「TwoFactorAuthenticationFilter」コードで次の「XSRF」更新コード ブロックのコメントを外すと、ダウンストリーム リクエスト ヘッダーは異なる/新しい「XSRF」トークン値を示しますが、同じエラーがスローされます。if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){ CsrfToken トークン = (CsrfToken) request.getAttribute("_csrf"); response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken()); } **これは、`/oauth/authorize` と `/oauth/token` が相互に通信できるように `XSRF` 設定を更新する必要があることを示しています。 `XSRF` トークン値。** おそらく、これを実現するには `CustomOAuth2RequestFactory` を変更する必要があります。しかし、どのように?
**関連コード:**
`CustomOAuth2RequestFactory` のコードは次のとおりです。public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) { super(clientDetailsService); } @Override public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) { ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpSession セッション = attr.getRequest().getSession(false); if (session != null) { AuthorizationRequest 認可リクエスト = (AuthorizationRequest) セッション。getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME); if (authorizationRequest != null) { session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME); 承認リクエストを返します。super.createAuthorizationRequest(authorizationParameters) を返します。} } `TwoFactorAuthenticationFilter` のコードは次のとおりです: //このクラスは次のように追加されます: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2 * 後で {@link com.example.CustomOAuth2RequestFactory} によって選択され、 * 承認フローを続行できるように、セッション内の authorizationRequest を使用します。*/ public class TwoFactorAuthenticationFilter extends OncePerRequestFilter { private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); プライベート OAuth2RequestFactory oAuth2RequestFactory; //これらの次の 2 つは、定義されていないときに発生したコンパイル エラーを回避するためのテストとして追加されます。public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED"; public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED"; @Autowired public void setClientDetailsService(ClientDetailsService clientDetailsService) { oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); } private boolean twoFactorAuthenticationEnabled(コレクション権限) { System.out.println(">>>>>>>>>>> 権限のリストには次が含まれます: "); for (GrantedAuthority 権限 : 権限) { System.out.println("auth: "+authority.getAuthority() ); anyMatch(権限 -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(権限.getAuthority())); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter. + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ); System.out.println("======================= twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) は: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) ); if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) || twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) { // authenticationRequest をセッションに保存します。// これにより、CustomOAuth2RequestFactory は、 // ユーザーが 2 要素認証を正常に実行した後に、この保存されたリクエストを AuthenticationEndpoint に返すことができます。request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest); // ユーザーが 2 要素認証コードを入力する必要があるページをリダイレクトします redirectStrategy.sendRedirect(request, response, ServletUriComponentsBuilder.fromCurrentContextPath() .path(TwoFactorAuthenticationController.PATH) .toUriString()); 戻る; //if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){ // CsrfToken token = (CsrfToken) request.getAttribute("_csrf"); // これは、ヘッダーまたは HTTP パラメーターとして含まれるトークンの値です // response.setHeader("XSRF-TOKEN", token.getToken()); //} filterChain.doFilter(リクエスト、レスポンス); } プライベート Map paramsFromRequest(HttpServletRequest リクエスト) { Map params = new HashMap(); for (エントリエントリ: request.getParameterMap().entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); パラメータを返します。} } } プライベート Map paramsFromRequest(HttpServletRequest リクエスト) { Map params = new HashMap(); for (エントリエントリ: request.getParameterMap().entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); パラメータを返します。} } } プライベート Map paramsFromRequest(HttpServletRequest リクエスト) { Map params = new HashMap(); for (エントリエントリ: request.getParameterMap().entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); パラメータを返します。} }
**コンピュータで問題を再現:**
次の簡単な手順に従って、数分で任意のコンピューターで問題を再現できます。 1.)このリンクをクリックして、ファイル共有サイトからアプリの圧縮バージョンをダウンロードします。. 2.) 「tar -zxvf oauth2.tar(2).gz」と入力してアプリを解凍します。 3.) 「oauth2/authserver」に移動し、「mvn spring-boot:run」と入力して「authserver」アプリを起動します。 . 4.) 「oauth2/resource」に移動してから「mvn spring-boot:run」と入力して、「resource」アプリを起動します。 5.) 「oauth2/ui」に移動してから「mvn」と入力して、「ui」アプリを起動します。 spring-boot:run` 6.) Web ブラウザーを開き、`http : // localhost : 8080` に移動します。クリックして送信します。8.) 「PIN コード」として「5309」を入力し、送信をクリックします。**これにより、上記のエラーが発生します。** 完全なソース コードを表示するには、a.) maven プロジェクトを IDE にインポートするか、b.) 解凍したディレクトリ内を移動してテキスト エディターで開きます。
このリンクをクリックする と、ファイル共有サイトですべての HTTP ヘッダーと Spring Boot ログを読み取ることができます。