シンプル HTTP リクエストと XHR リクエストの違いを理解します。
Origin Web アプリ - https://github.com
「Origin Web App」と入力して Web サイトを開くと、ブラウザーは単純な HTTP リクエストを作成します。ここでは CORS は適用されません。 Origin Web App からコンテンツを提供することを自発的にリクエストしているためです。あなたのブラウザは問題なくサービスを提供するでしょう。ただし、XHR リクエストに関しては、Origin Web App によってブラウザに読み込まれる JavaScript が、リソースを要求する任意のサーバー (https://netflix.com) にリクエストを送信できます。現在、Origin Web App は Github によって所有されていますが、https://netflix.com は Netflix によって所有されており、そのサーバーは潜在的にあらゆるサービスを提供できる可能性があります。 Github は、Netflix が所有するサーバーを制御することはできません。これは、ある Web サイト (金融 Web サイトの可能性があります) から任意のリモート サーバーにコンテンツを盗む可能性があり、セキュリティに多くの影響を及ぼします。
幸いなことに、CORS は、指定された一連のルールを使用して、この問題に非常にうまく取り組んでいます。
これは、クライアント (ブラウザ) とサーバー間のクロスオリジン リクエストを有効にして、セキュリティを維持しながらリソースを共有できるようにするために W3C によって定義された標準です。すべてのブラウザはこれらの標準に準拠し、サードパーティのサーバーからリソースが読み込まれるのを防ぎます。
ヘッダーはリクエストの送信元を示します。これは、CORS リクエストおよび POST リクエストとともに送信されます。
構文:
Origin: <scheme> "://" <hostname> [":" <port> ]
例:
Origin: https://netflix.com
Origin: http://netflix.com:443
Origin: http://localhost:1443
このヘッダーは、プリフライト リクエストを発行するときにブラウザによって使用され、実際のリクエストが行われるときにどのリクエスト メソッドが使用されるかを示します。
構文:
Access-Control-Request-Method: <method>
<method>
、 GET
、 POST
、またはDELETE
いずれかです。
例:
Access-Control-Request-Method: POST
このヘッダーは、実際のリクエストが行われるときにどのリクエスト ヘッダーが使用されるかを示すために、ブラウザーによるプリフライト リクエストでも再度使用されます。複数のヘッダーを使用するには、カンマで区切る必要があります。
構文:
Access-Control-Request-Headers: <header-name>
Access-Control-Request-Headers: <header-name>, <header-name>
例:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
重要:すべてのヘッダーは CORS セーフリストに登録されているか、 X-Custom-Header
などのカスタム ヘッダーである必要があります。
Accept
Accept-Language
Content-Language
Content-Type
許可される値はapplication/x-www-form-urlencoded
、 multipart/form-data
、およびtext/plain
です。
次のヘッダーがプリフライト リクエストで返されます。
このヘッダーは、要求されたオリジンが許可されているかどうかを示します。ブラウザは、リクエストされたオリジンとこれを照合することで、リクエストの成功/失敗を選択します。
構文:
Access-Control-Allow-Origin: *
認証情報のないリクエストの場合、値*
をワイルドカードとして指定できます。これにより、ブラウザにどの Origin からのリクエストも許可するように指示されます。
Access-Control-Allow-Origin: <origin>
応答ヘッダーでオリジンを 1 つだけ受信する場合、それは、リクエストされたOrigin
に基づいたサーバー/Web アプリが、許可されている場合は同じOrigin
で応答することを意味します。サーバーは、要求ヘッダーに基づいて変化することを示すために、Vary で応答する必要もあります。
例:
Access-Control-Allow-Origin: https://github.com
このヘッダーの値のいずれかが一致すると、ブラウザーは実際のリクエストを実行します。ワイルドカード*
が返された場合は、任意のメソッドが許可されることを意味します。
構文:
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
例:
Access-Control-Allow-Methods: GET, POST
リクエストされたヘッダーがすべて許可されている場合、ブラウザは実際のリクエストを実行します。
構文:
Access-Control-Allow-Headers: <header-name>, <header-name>
Access-Control-Allow-Headers: *
例:
Access-Control-Allow-Headers: Accept, Content-Type
ワイルドカード*
ブラウザにあらゆるヘッダーを許可するように指示します。
プリフライトの結果をキャッシュできる期間を示します。
Access-Control-Max-Age: <delta-seconds>
最大数結果をキャッシュできる秒数。
各ブラウザには上限があり、
一般にキャッシュ目的で使用されるヘッダーを変更して、キャッシュされた応答を使用できるか、ヘッダー値に基づいて再キャッシュする必要があるかを判断します。
CORS では、サーバーがリクエストされたOrigin
に基づいて複数のオリジンを許可し、 Access-Control-Allow-Origin
で特定の URL を返すとします。
構文:
Vary: <header-name>
例:
github.com が https://github.com と https://netflix.com の両方でリソースのリクエストを許可しているとします。次のシナリオを考えてみましょう。
シナリオ 1:
カール -X オプション https://github.com/api/v1/gists/1
Origin: https://github.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://github.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
このシナリオでは、ブラウザーはこのプリフライト要求の結果を 10 分間キャッシュします。
シナリオ 2:
カール -X オプション https://github.com/api/v1/gists/1
Origin: https://netflix.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://netflix.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
Access-Control-Allow-Origin
に https://netflix.com が含まれていることがわかります。これは、指定されたOrigin
に基づいて応答がどのように変化するかを示す例です。最初のシナリオとは異なり、この応答の最大保存期間も 5 分間のみキャッシュされます。
一部のリクエストは CORS のプリフライトリクエストをトリガーしません。これらを単純リクエストと呼びます。次の条件を満たす必要があります。
許可されている方法の 1 つ:
ユーザー エージェントによって自動的に設定されるヘッダー (たとえば、 Connection
またはUser-Agent
) を除き、手動で設定できるヘッダーは、CORS セーフリストに登録されている要求ヘッダーと次のヘッダーのみです。
DPR
Downlink
Save-Data
Viewport-Width
Width
リクエストで使用されるXMLHttpRequestUpload
オブジェクトにはイベント リスナーが登録されていません。
リクエストではReadableStream
オブジェクトは使用されません。
例:
リクエスト:
GET /api/v1/public-data/ HTTP/1.1
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
応答ヘッダー:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
リクエストがAccess-Control-Allow-Origin
ヘッダーで*
を返した場合。これは、どのHost
からでもリクエストを行うことができることを意味します。この場合、ブラウザはプリフライトリクエストを行いません。
ブラウザは、実際のリクエストが安全に送信できるかどうかを判断するために、プリフライト リクエストを作成します。
例:
const xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , 'https://bar.com/api/v1/post-here/' ) ;
xhr . setRequestHeader ( 'X-PINGOTHER' , 'pingpong' ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json;charset=UTF-8' ) ;
xhr . onreadystatechange = handler ;
xhr . send ( JSON . stringify ( { "email" : "[email protected]" } ) ) ;
飛行前のリクエスト:
OPTIONS /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept
実際のリクエスト:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
X-PINGOTHER
などの非標準ヘッダーが設定されている場合、ブラウザーはリクエストを安全に実行できるかどうかを判断できません。安全性を確保するために、ブラウザはX-PINGOTHER
とContent-Type
を含むAccess-Control-Request-Headers
を使用して OPTIONS リクエストを作成します。プリフライトリクエストのレスポンスヘッダーで検証すると、ブラウザは実際のリクエストを作成します。
一般に、XHR リクエストを行う場合、Cookie はリクエストとともに渡されません。 Cookie を渡す必要がある場合は、 XMLHttpRequest
オブジェクトにフラグを設定する必要があります。
const xhr = new XMLHttpRequest ( ) ;
const url = 'http://bar.com/api/v1/credentialed-content/' ;
function callOtherDomain ( ) {
if ( invocation ) {
xhr . open ( 'GET' , url , true ) ;
xhr . withCredentials = true ;
xhr . onreadystatechange = handler ;
xhr . send ( ) ;
}
}
リクエスト:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
Cookie: _session=NyV14oKXiS6HHopaf9nT
リクエストがXMLHttpRequest
で行われ、 withCredentials
フラグが設定されている場合、ブラウザはリクエスト ヘッダーでCookie
を渡します。
応答:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: _session=AjBSqxj4T7bSySNTWeEm; expires=Wed, 31-05-2020 00:00:00 GMT
ブラウザーがAccess-Control-Allow-Credentials
が true に設定されていることに気付いたとき。 Set-Cookie
ヘッダーを尊重し、Cookie を設定します。
重要: 「資格情報付きリクエストとワイルドカード」セクションで説明したように、「*」ワイルドカードをAccess-Control-Allow-Origin
に設定しないでください。
withCredentials
フラグを設定して資格情報リクエストが行われる場合、どのヘッダーにアクセスできるかをブラウザーに知らせるために、サーバーによってAccess-Control-Expose-Headers
設定する必要があります。
Cors 以前の世界では、デフォルトでは、CORS リクエストの応答ヘッダーにブラウザーはアクセスできません。したがって、ブラウザが公開されたヘッダーを読み取るためにこのヘッダーを探すように明示的にされています。このように CORS 仕様により、古いブラウザが壊れることはありません。
これはプリフライトリクエストで返されます。ブラウザがこれを認識すると、 Set-Cookie
ヘッダーにアクセスできます。上で述べたように、通常の XHR リクエストでは、ブラウザはSet-Cookie
レスポンス ヘッダーを読み取るだけでなく、リクエスト ヘッダーでCookie
渡しません。
構文:
Access-Control-Allow-Credentials: true
例は、「認証情報を使用したリクエスト」セクションにあります。
資格情報付きリクエストという場合は、XHR リクエストで渡される、または応答ヘッダーSet-Cookie
を介して設定される Cookie を意味します。 withCredentials フラグを使用して XHR リクエストが行われた場合、設定される Cookie とともに応答を受け取ることを期待します。ただし、 Access-Control-Allow-Origin
が「*」であることは期待できません。これは、どの Web サイトでもこれらの Cookie を使用できることを意味するためです。このため、 Access-Control-Allow-Origin
応答ヘッダーに「*」が含まれる場合、ブラウザーは要求を失敗させます。
プリフライトリクエストが 301/302 で応答する場合、一部のブラウザは現在これをサポートしていない可能性があります。次のようなエラーが発生する場合があります。
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
注:回避策については、Mozilla によるプリフライト要求とリダイレクトのドキュメントを確認してください。
ユーザーが有効にしている場合、ブラウザにはすべてのthird-party
Cookie を拒否する設定があります。たとえば、リクエストがhttps://foo.com
から行われ、サーバーがhttps://bar.com
にある場合、ブラウザはhttps://bar.com
によって送信される Cookie を設定しません。