1. 概要

この短いチュートリアルでは、クロスオリジン通信をサポートし、SpringSecurityを使用するアプリケーションで発生する可能性があるエラー「プリフライトの応答に無効なHTTPステータスコード401があります」を解決する方法を学習します。

まず、クロスオリジンリクエストとは何かを確認してから、問題のある例を修正します。

2. クロスオリジンリクエスト

クロスオリジンリクエストは、要するに、リクエストのオリジンとターゲットが異なるHTTPリクエストです。 これは、たとえば、Webアプリケーションが1つのドメインから提供され、ブラウザーがAJAX要求を別のドメインのサーバーに送信する場合です。

クロスオリジンリクエストを管理するには、サーバーはCORSまたはクロスオリジンリソースシェアリングと呼ばれる特定のメカニズムを有効にする必要があります。

CORSの最初のステップは、 OPTIONS リクエストであり、リクエストのターゲットがそれをサポートしているかどうかを判断します。 これは飛行前リクエストと呼ばれます。

サーバーは、ヘッダーのコレクションを使用して飛行前の要求に応答できます。

  • Access-Control-Allow-Origin:どのオリジンがリソースにアクセスできるかを定義します。 「*」は任意の起源を表します
  • Access-Control-Allow-Methods:クロスオリジンリクエストで許可されるHTTPメソッドを示します
  • Access-Control-Allow-Headers:クロスオリジンリクエストで許可されるリクエストヘッダーを示します
  • Access-Control-Max-Age:キャッシュされたプリフライトリクエストの結果の有効期限を定義します

したがって、プリフライトリクエストがこれらのレスポンスヘッダーから決定された条件を満たさない場合、実際のフォローアップリクエストはクロスオリジンリクエストに関連するエラーをスローします。

Spring-powered service にCORSサポートを追加するのは簡単ですが、正しく構成されていない場合、このプリフライトリクエストは常に401で失敗します。

3. CORS対応のRESTAPIの作成

問題をシミュレートするために、最初にクロスオリジンリクエストをサポートする単純なRESTAPIを作成しましょう。

@RestController
@CrossOrigin("http://localhost:4200")
public class ResourceController {

    @GetMapping("/user")
    public String user(Principal principal) {
        return principal.getName();
    }
}

@CrossOrigin アノテーションは、引数で言及されているオリジンからのみAPIにアクセスできるようにします。

4. RESTAPIの保護

SpringSecurityを使用してRESTAPIを保護しましょう。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .httpBasic();
    }
}

この構成クラスでは、すべての着信要求に承認を適用しました。 その結果、有効な認証トークンがない場合はすべてのリクエストが拒否されます。

5. 飛行前のリクエストを行う

REST APIを作成したので、curlを使用して飛行前のリクエストを試してみましょう。

curl -v -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:4200" 
  -X OPTIONS http://localhost:8080/user
...
< HTTP/1.1 401
...
< WWW-Authenticate: Basic realm="Realm"
...
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Credentials: true
< Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
...

このコマンドの出力から、リクエストが401で拒否されたことがわかります。

これはcurlコマンドであるため、出力に「プリフライトの応答に無効なHTTPステータスコード401があります」というエラーは表示されません。

ただし、別のドメインからREST APIを使用するフロントエンドアプリケーションを作成し、それをブラウザーで実行することで、この正確なエラーを再現できます。

6. ソリューション

Spring Security構成では、プリフライトリクエストを承認から明示的に除外していません。 Spring Securityは、デフォルトですべてのエンドポイントを保護することに注意してください。

その結果、 APIは、OPTIONSリクエストでも認証トークンを必要とします。

Springは、OPTIONSリクエストを承認チェックから除外するためのすぐに使えるソリューションを提供します。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
        http.cors();
    }
}

cors()メソッドは、Springが提供する CorsFilter をアプリケーションコンテキストに追加し、OPTIONSリクエストの承認チェックをバイパスします。

これで、アプリケーションを再度テストして、機能していることを確認できます。

7. 結論

この短い記事では、SpringSecurityおよびクロスオリジンリクエストにリンクされているエラー「プリフライトの応答に無効なHTTPステータスコード401があります」を修正する方法を学びました。

この例では、問題を再現するためにクライアントとAPIを異なるドメインまたはポートで実行する必要があることに注意してください。たとえば、デフォルトのホスト名をクライアントにマップし、マシンのIPアドレスをRESTにマップできます。ローカルマシンで実行する場合のAPI。

いつものように、このチュートリアルに示されている例は、Githubにあります。