系统服务化构建-跨域CROS

  • 2019 年 12 月 10 日
  • 筆記

文本讨论关于接口开发中的跨域 CORS。CORS 是一种浏览器协议,源于 HTTP 请求的安全策略,在这个体系中的关键词有,同源策略,XMLHttpRequest,Ajax,和前后端分离。

尤其是在目前业界前后端分离的大趋势下,跨域是一种常见的前后端开发通讯(communicate)方式。

“The basic idea behind CORS is to use custom HTTP headers to allow both the browser and the server to know enough about each other to determine if the request or response should succeed or fail.

“以上一段话参考 Cross-domain Ajax with Cross-Origin Resource Sharing/[1],主要含义是说 CORS 的核心思想是通过 HTTP 请求头通讯,使得客户端和服务器端彼此决定请求和响应是否被成功接受。

Using CORS[2]这里有一篇关于跨域技术的完整阐述,感兴趣的可以阅读。

“CORS 协议的实现需要客户端和服务器端配合协作完成。也就是我们通常所说的跨域设置。

The browser adds some additional headers, and sometimes makes additional requests, during a CORS request on behalf of the client

简单请求

浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

Microsoft API 设计指导 [3] 中这么一段关于跨域的描述

8.1.1. Avoiding preflight

Because the CORS protocol can trigger preflight requests that add additional round trips to the server, performance-critical apps might be interested in avoiding them.

The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make. All other requests require preflight.

A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded," "multipart/form-data" or "text/plain." For any other headers or values, a preflight request will happen.

“The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make

我们提炼出简单请求的判断标准

1 )GET, HEAD or POST 三种请求 2) 不增加任何额外的请求头 3) POST 请求允许三种 Content—Type:"application/x-www-form-urlencoded," "multipart/form-data" or "text/plain.

非简单请求

不符合简单请求的,均属于非简单请求。这么看来我们平时经常使用的

Content-Type: application/json

显然是一种非简单请求头。

对于非简单请求,CORS 机制会自动触发浏览器首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。这里的 preflight 也可以被翻译为(预检)请求。

CROS 流程.png

Access-Control-Allow-Headers

像上文中所说那样, 增加了自定义字段后,跨域请求就变成了一种带有 preflight 的非简单请求,因此会有下面的一种理解。

“Access-Control-Allow-Headers 是 preflight 请求中用来标识真正请求将会包含哪些头部字段,也就是下文中的自定义头部。这种方式是服务器端安全防范的一种。

Access-Control-Allow-Credentials

这个设置是关于是否支持 Cookies 的

xhr.withCredentials = true;    Access-Control-Allow-Credentials: true

YII2 中的跨域设置

//Access-Control-Allow-Origin:*      public $arr_acao = [          '*'      ];      //Access-Control-Allow-Methods      public $arr_acam = [          'POST',          'PUT',          'GET',          'DELETE',          'OPTIONS'      ];      //Access-Control-Allow-Headers      public $arr_acah = [          'token',          'app-key',          'content-type',      ];     header('Access-Control-Allow-Origin: '.implode(',', $this->arr_acao));          header('Access-Control-Allow-Methods: '.implode(',', $this->arr_acam));          header('Access-Control-Allow-Headers: '.implode(',', $this->arr_acah));          header("Access-Control-Max-Age: 86400");            if ($req->isOptions) {              $code = "202";              $message = "Accepted";              header("HTTP/1.1 ".$code." ".$message);              exit();          }

通过 Access-Control-Allow-Headers 设置自定义字段的方式,是一种安全策略,服务端要求请求头必须携带 'token', 'app-key','content-type'三个属性字段,缺一不可,否则请求不能达成。

origin 'http://xx.cn' has been blocked by CORS policy:  Request header field timestamp is not allowed by Access-Control-Allow-Headers in preflight response.

Nginx 中的跨域

location ~* .(eot|ttf|woff|woff2|svg)$ {      add_header Access-Control-Allow-Origin *;      add_header Access-Control-Allow-Headers X-Requested-With;      add_header Access-Control-Allow-Methods GET,POST,OPTIONS;  }

以上设置的前提是 Nginix 开启了模块 ngx_http_headers_module[4]

Nginx 中的其它 CORS 配置

#  # 用于nginx的开放式CORS配置  #  location / {       if ($request_method = 'OPTIONS') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          #          # 自定义标题和标题各种浏览器*应该*可以,但不是          #          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          #          # 有效期为          #          add_header 'Access-Control-Max-Age' 22000;          add_header 'Content-Type' 'text/plain; charset=utf-8';          add_header 'Content-Length' 0;          return 204;       }       if ($request_method = 'POST') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';       }       if ($request_method = 'GET') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';       }

Nginx 中跨域,应用程序与 web 服务器存在耦合,增加了应用程序部署和扩展的复杂性,按需使用。

CROS 总结

本文主要介绍了 CROS 的基本分类和常见的实现方案,对于同源策略,XMLHttpRequest 请求等基础知识被没有过多涉及。

简单请求和非简单请求的分类是重点。理解了这一点,就能理解什么场景浏览器会发起预检请求,并回复对应的响应。

我们常说跨域设置是客户端和服务器端一起配合的结果,官方协议更倾向于让开发者对于跨域无感知,而浏览器与后端服务的交互和相互信任是核心。

参考资料

[1]

Cross-domain Ajax with Cross-Origin Resource Sharing/: https://humanwhocodes.com/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/

[2]

Using CORS: https://www.html5rocks.com/en/tutorials/cors/

[3]

Microsoft API设计指导 : https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md

[4]

ngx_http_headers_module: http://nginx.org/en/docs/http/ngx_http_headers_module.html