文章目录
一、同源策略/SOP(Same origin policy)
1.1 源(origin)的定义
以下三个部分共同构成了一个源:
-
协议:如http、https、ftp等;
-
域名:如
www.baidu.com
、www.google.com
等; -
端口:如443、80等。
1.2 同源的含义
如果两个 URL 的源完全相同的话,则这两个 URL 是同源。
举例来说, http://store.company.com/dir/page.html
与下面的 URL 比较:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 协议不同 |
http://store.company.com:81/dir/etc.html | 失败 | 端口不同 ( http:// 默认端口是80) |
http://news.company.com/dir/other.html | 失败 | 主机不同 |
1.3 同源策略
同源策略是一个重要的安全策略,它是浏览器最核心、最基本的安全功能。它用于限制非同源的站点之间如何进行数据交互,极大程度上防止了如CSRF、XSS等网络攻击。
具体的限制措施如下:
- 无法读取对方的 Cookie、LocalStorage 和 IndexDB。
- 无法获得对方的 DOM。
- 不能向对方发送 AJAX 请求。
二、CORS(跨源资源共享)
由于浏览器的同源策略,浏览器不能向不同源的URL发送AJAX请求,但有时候还就需要这样做。这个问题有几种解决方案,但最终极的解决方案是CORS。
CORS是一个W3C标准,全称是“跨源资源共享”(Cross-origin resource sharing)。它允许浏览器不同源的URL发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS是基于HTTP头的,它新增了一组 HTTP 首部字段,允许服务器声明哪些源站可以通过浏览器访问哪些资源。
2.1 预检请求
对于某些请求,**浏览器必须首先使用 OPTIONS
方法发起一个预检请求,从而获知服务端是否允许该跨源请求。**服务器确认允许之后,才发起实际的 HTTP 请求。
2.2 请求分类
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求不会触发CORS预检请求,而非简单请求则必须发送预检请求。
满足以下条件的,属于简单请求:
- 请求方法是以下三种之一:
- HEAD
- GET
- POST
- HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
- 请求中的任意
XMLHttpRequest
对象均没有注册任何事件监听器;XMLHttpRequest
对象可以使用XMLHttpRequest.upload
属性访问。 - 请求中没有使用
ReadableStream
对象。
而凡是不同时满足上面的任何一个条件,就属于非简单请求。
2.3 简单请求
对于简单请求,浏览器在头信息之中,增加一个Origin
字段,然后直接发出CORS请求。请求报文的例子如下:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
请求首部字段Origin
表明该请求来源于 http://foo.example
。
如果该源在服务器的允许范围之内,那么服务器就会返回一个携带了Access-Control-Allow-Origin
头部信息的HTTP响应:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
Access-Control-Allow-Origin: *
表明,该资源可以被任意外域访问。
如果该源不在服务器的允许范围之内,那么服务器就会返回一个正常的HTTP响应。但浏览器发现响应没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。
2.4 非简单请求
浏览器发出CORS非简单请求,会在正式通信之前先发送预检请求。询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。
预检请求示例:
OPTIONS /resources/post-here/ HTTP/1.1
Host: qcloud.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://apigw.qcloud.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method
告诉服务器发的请求是POST请求,Access-Control-Request-Headers
通知自己带有X-PINGOTHER
自定义header。
响应报文示例:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://apigw.qcloud.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Access-Control-Allow-Origin
与前面类似,Access-Control-Allow-Methods
这里说明支持POST/GET/OPTIONS方法,Access-Control-Allow-Headers
这里说明允许X-PINGOTHER
自定义header,Access-Control-Max-Age
用来指定本次预检请求的有效时间,86400是24小时也就是一天。
一旦服务器通过了”预检”请求,在Access-Control-Max-Age
指定的时间内,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
三、使Django项目支持CORS
3.1 自定义中间件
具体实现这里就不讲了,大体思路就是根据请求报文判断请求类型,然后再去做响应的处理。
3.2 使用django-cors-headers
3.2.1 安装
-
使用pip安装:
pip install django-cors-headers
-
注册app:
INSTALLED_APPS = [ ..., "corsheaders", ..., ]
-
添加到中间件:
MIDDLEWARE = [ ..., "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", ..., ]
CorsMiddleware
应该放在尽可能高的位置,特别是在任何能够生成响应的中间件之前,比如Django的CommonMiddleware
或Whitenoise的WhiteNoiseMiddleware
。如果没有,它将不能向这些响应添加CORS头。
3.2.2 配置
在Django设置中配置中间件的行为,以下三种配置的至少配置一种:
-
CORS_ALLOWED_ORIGINS
:允许CORS的源。CORS_ALLOWED_ORIGINS = [ "https://example.com", "https://sub.example.com", "http://localhost:8080", "http://127.0.0.1:9000", ]
-
CORS_ALLOWED_ORIGIN_REGEXES
:与上面的配置项类似,只不过支持正则表达式。CORS_ALLOWED_ORIGIN_REGEXES = [ r"^https://\w+\.example\.com$", ]
-
CORS_ALLOW_ALL_ORIGINS
:是否允许所有的源进行CSOR请求,默认为False。
还有一些其他的配置:
-
CORS_ALLOW_CREDENTIALS
:如果为True,则允许在跨源的HTTP请求中包含cookie。默认值为False。 -
CORS_ALLOW_METHODS
:允许跨源请求的请求方法。CORS_ALLOW_METHODS = [ "DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT", ]
更详细的配置参考官方文档:传送门