HTTP 跨域资源共享

2,460 阅读5分钟

跨域资源共享(CORS, Cross-Origin Resource Sharing) 是浏览器的机制,让浏览器可以合理避开浏览器同源策略的限制,以访问不同源上的资源。

跨域请求

访问不同的域、协议或端口的资源时,浏览器会发起一个跨域请求。

以下展示了从 http://www.a.com/page.html 访问不同资源的情况

// 不同文件,不跨域
http://www.a.com/other.html 
// 不同目录,不跨域
http://www.a.com/path/other.html

// 不同协议,跨域 
https://www.a.com/other.html 

// 不同端口,跨域 
http://www.a.com:8080/other.html

// 不同域名,跨域 
http://www.b.com/other.html
// 不同域名(父域名),跨域 
http://a.com/other.html
// 不同域名(子域名),跨域 
http://sub.a.com/other.html
// 不同域名(ip),跨域 
http://119.254.98.162/other.html

预检请求(preflight request)

浏览器在发起跨域请求前会先使用 OPTIONS 方法发起一个预检请求,以获知服务器是否允许该跨域请求,服务器确认允许后才发起正式 HTTP 请求。

在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证。

简单请求

浏览器将跨域请求分为简单请求与非简单请求,简单请求不需要触发预检请求

满足发下条件会被视为简单请求:

  • 使用下列请求方法之一:
    • GET
    • HEAD
    • POST
  • 请求头信息不超出以下几种:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type 仅限于以下三者
      • text/plain

      • multipart/form-data

      • application/x-www-form-urlencoded

简单请求会在请求头添加 Origin 字段,服务器根据这个值来决定是否同意这次请求。

如果同请求,服务器响应会额外添加几个字段:

  • Access-Control-Allow-Origin(必须) 指定了允许访问该资源的域,其值要么是请求时 Origin 的值,要么是 * 表示接受任意域名的请求; 当指定的值不是 * 时,响应首部中的 Vary 字段的值必须包含 Origin;
  • Access-Control-Allow-Credentials(可选) 当浏览器的credentials设置为true时,是否允许浏览器读取response的内容; 返回true(唯一有效值,区分大小写)则可以,其他值均不可以。 此字段返回true时,Access-Control-Allow-Origin 必须为请求 Origin 的值,不能是 *; 此字段在预检请求的响应中时,它指定了实际的请求是否可以使用credentials; 简单请求不会被预检,当浏览器的credentials设置为true时,其响应中如果不包含该字段,浏览器将无法读取response的内容。
  • Access-Control-Expose-Headers(可选): 白名单,列出了服务器允许浏览器访问的头; 默认情况下,XMLHttpRequest.getResponseHeader() 方法只能拿到六种简单响应首部,如果想要能拿到其它字段,就需要在Access-Control-Expose-Headers指定。 六种简单响应首部是:
    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

非简单请求

非简单请求是那种对服务器有特殊要求的请求

比如请求方法是 PUTDELETE; 比如包含了限定以外的请求头,如 AuthorizationX-Custom-Header; 比如 Content-Type 字段值是 application/json;

对于非简单请求,浏览器会先发起一个预检请求,得到肯定答复后,才会发起正式请求。

预检请求

预检请求使用 OPTIONS 方法,包括以下字段:

  • Origin: 表明实际请求的源站;
  • Access-Control-Request-Method: 表明实际请求所使用的 HTTP 方法;
  • Access-Control-Request-Headers: 表明实际请求所额外携带的头部字段;

注:预检请求由浏览器自动发出,无须手动设置这些字段。

预检请求的响应

服务器收到预检请求后,检查了 Origin, Access-Control-Request-Method, Access-Control-Request-Headers 后,可以确认是否允许跨源请求,然后做出回应。

响应包括以下字段:

  • Access-Control-Allow-Origin 指定了允许访问该资源的域,含义同简单请求
  • Access-Control-Allow-Methods 指定了实际请求中允许使用的所有HTTP方法
  • Access-Control-Allow-Headers 指定了实际请求中允许携带的所有首部字段
  • Access-Control-Allow-Credentials 指定了实际的请求是否可以使用credentials,含义同简单请求
  • Access-Control-Max-Age 指定了预检请求的结果能够被缓存多久,单位秒

正常跨域请求

通过预检后,正常跨域请求跟简单请求一样,浏览器会在请求头添加 Origin 字段;

服务器的回应也跟简单请求一样,必须包含 Access-Control-Allow-Origin 字段。

PHP 跨域中间件

支持PHP-PSR规范的跨域资源共享(cors)中间件。

安装依赖

composer require reezy/cors 

配置

<?php

return [
    // 是否允许所有的域
    'allowed-all-origins'   => env('APP_ENV') !== 'prod',
    // 允许访问该资源的域
    'allowed-origins'       => ['http://localhost'],
    // 实际请求中允许携带的所有首部字段
    'allowed-headers'       => ['authorization', 'content-type', 'x-requested-with'],
    // 实际请求中允许使用的所有HTTP方法
    'allowed-methods'       => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], // ex: GET, POST, PUT, PATCH, DELETE
    // 实际的请求是否可以使用credentials
    'allowed-credentials'   => true,
    // 白名单,列出了服务器允许浏览器访问的头
    // 六种简单首部不需要指定:Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma
    'exposed-headers'       => [ ],
    // 预检请求的结果能够被缓存多久,单位秒
    'max-age'               => 10 * 86400,
];

参考

HTTP访问控制(CORS)
developer.mozilla.org/zh-CN/docs/…
浏览器的同源策略
developer.mozilla.org/zh-CN/docs/…