跨域

跨域指的就是“跨域资源共享(Cross-Origin Resource Sharing, CORS)”,是一个“W3C标准”,当一个资源从与该资源本身所在的服务器的不同域或者不同端口请求一个资源时,就会发起一个跨域HTTP请求。

说到跨域,肯定就要讲一下 同源策略(Same origin policy),该策略是由Netscape(网景)公司在1995年引入浏览器的一个著名的安全策略,是一种约定,也是浏览器最核心,最基本的安全功能,如果少了同源策略,浏览器的正常功能可能都会收到影响,可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。目前所有支持JavaScript的浏览器都会使用这个策略。

 

同源的目的

为了保证用户信息的安全,防止恶意网站窃取数据
设想这样一种情况:A网站是一家银行,用户登录后,产生了Cookie,在没有同源策略的情况下,又去浏览其他网站,则其他网站可以读取到A网站的Cookie,会发生什么?很显然,如果Cookie 包含隐私(比如存款总额)这些信息就会泄露,更可怕的是,Cookie往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。
所以同时打开的B网页,不能访问A页的Cookie,除非这两个网页“同源”,如果非同源,那么请求数据的时候,浏览器会在控制台中报一个异常,提示拒绝访问。

什么是同源

协议相同:比如http://
域名相同:比如“www.test.com”
端口相同:比如同为8000(默认端口80可以省略)
同时满足满足以上三个条件的才可以被称为同源

举例说明:http://www.example.com/dir/page.html 这个网址,协议是http://, 域名是www.example.com,端口是80(默认端口可以省略),它的同源情况如下:

A.http://www.example.com/dir2/other.html 同源
B.http://www.example.cn/dir/other.html 不同源(域名不同)
C.http://example.com/dir/other.html 不同源(域名不同)
D.http://v2.www.example.com/dir/other.html 不同源(域名不同)
E.http://www.example.com:81/dir/other.html 不同源(端口不同)

 

非同源的限制

1.Cookie、LocalStorage、IndexDB 无法读取
2.DOM 无法获得
3.AJAX 请求不能发送
由于本文主要是介绍Django目前最优的解决跨域请求的方法,对避免上述三种限制有兴趣的小伙伴可以去阮老师的《浏览器同源策略及其规避方法》的http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

 

回归Django的跨域的解决方案

参考资料:https://github.com/ottoyiu/django-cors-headers/
原作者测试的组合:
Python:2.7、 3.6
Django:1.8、1.9、1.10、1.11、2.0、2.1
我所使用的组合:
Python:3.6 Django:1.11.11
具体步骤:

1.pip 安装:
在所使用的虚拟环境中安装:
pip install django-cors-headers

2.将其注册到Django 项目setting的INSTALLED_APPS中:
INSTALLED_APPS = (
...,
'corsheaders',
...

3.添加中间件类来监听响应
# ‘CorsMiddleware’ 图省事可以放在最外层
推荐尽可能放置在生成响应的任何中间件之前,
如Django 'CommonMiddleware' ,如果不是,则会跨域失败

MIDDLEWARE = [
...,
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]

4.需要在Django中使用的配置

CORS_ORIGIN_WHITELIST
允许白名单,允许执行跨站请求的主机列表(疑问添加'null',待解决),默认是:[ ]
如:
CORS_ORIGIN_WHITELIST = (
'12.0.0.1:8000',
'google.com',
'localhost:8000',
)

CORS_ALLOW_CREDENTIALS
跨域请求时是否允许携带Cookie,默认是False
CORS_ALLOW_CREDENTIALS = True

# 实现以上配置功能即可进行跨域

 

# 配置扩展(以下配置,根据需求进行添加)

CORS_ORIGIN_ALLOW_ALL
如果是True,将不再使用白名单,并允许所有主机执行跨站点请求,默认是False

CORS_ORIGIN_REGEX_WHITELIST
当你有大量子域名需要添加到白名单的时候,‘CORS_ORIGIN_WHITELIST’ 就不太适用了,可以通过此设置将对HTTP请求进行正则匹配,匹配成功则允许跨域,否则不允许,默认是[ ]
如:
CORS_ORIGIN_REGEX_WHITELIST = (
r'^(https?://)?(\w+\.)?google\.com$',
r'^(https?://)?(\w+\.)?baidu\.com$',
)

CORS_URLS_REGEX
一个正则表达式,限制所有进行CORS 的URL,默认是 r'^.*' 即匹配所有URL,当您只需要一部分请求CORS时,很有用,例如API /api/:
CORS_URLS_REGEX = r"^/api/.*$"

CORS_ALLOW_METHODS
允许跨域的请求方式,一般使用默认,如下:
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
扩展添加额外的请求方式时可以导入默认值,如:
from corsheaders.defaults import default_methods
CORS_ALLOW_METHODS = default_methods + (
'POKE' # 自定义请求方式
)

CORS_ALLOW_HEADERS
允许跨域的请求头,一般使用默认值,如:
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
同上一样,可以导入默认值,来实现自定义请求头的扩展
from corsheaders.defaults import default_headers
CORS_ALLOW_HEADERS = default_headers + (
'my-custom-header',
)

CORS_EXPOSE_HEADERS
向浏览器公开的HTTP请求头列表,默认是 [ ]

CORS_MODEL
几乎用不到,留坑,以后扩展。默认为None

CORS_PREFLIGHT_MAX_AGE
浏览器或者客户端可以缓存预检响应的秒数,如果是0或者任何假值,则再次发送请求时,还需要进行预检,默认是86400(一天)
扩展:预检是发送“非简单请求”时,发送真正请求前的额外请求

简单请求与非简单请求

同时满足以下两大条件,就属于简单请求,否则就是复杂请求
一、请求方式
1.HEAD
2.GET
3.POST
二、请求头信息
1.Accept
2.Accept-Language
3.Content-Language
4.Last-Event-ID
5.Content-Type 只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain

针对简单请求和复杂请求,浏览器的处理方式

简单请求
浏览器直接发出CORS 请求 ,具体来说就是在请求头信息中添加一个"Origin"的字段
Origin字段的作用,就是说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意这次请求
1.如果Origin指定的源不在许可范围内,服务器会返回一个头信息里面不包含“Access-Control-Allow-Origin”字段的正常响应,浏览器发现没有该字段后,就会抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,因为这种HTTP回应的状态码可能是200,所以无法通过状态码识别或进行条件判断。
2.如果Origin指定的源在许可范围内,服务器的返回的响应里面,会多出四个头信息字段,其中三个字段与CORS请求相关,且都是以“Access-Control-” 开头

Access-Control-Allow-Origin
该字段是必须的,它的值要么是请求时Origin字段的值,要么时一个“*”,表示接收任意域名的请求

Access-Control-Allow-Credentials
该字段可选,值是一个布尔值,表示是否允许发送Cookie,默认为False
如果需要将Cookie 包含在请求头中,一起发给服务器,则必须将此字段设为True

Access-Control-Expose-Headers
该字段可选,CORS请求时,XMLHttpRequest 对象的getResponseHeader()方法只能拿到6个基本字段“Cache-Control(缓存控制)”,“Content-Language(指明报文体使用的语言,譬如:ch,fr,en,ja等等)”,“Content-Type(指定报文体的类型,比如text/xml,image/jpeg等等,同时可以通过charset来指定内容所使用的字符集)”,“Expires(用来控制缓存的失效期,如果Cache-Control设置“max-age”或"s-max-age",则会忽略该字段)”,“Last-Modified(请求成功后用来标记此文件在服务器端最后被修改的时间,如果第二次进行请求发现服务器资源没有发生变化,此字段值不变,则自动重定向并返回304(Not Changed)的状态码)”,“Pragma(HTTP/1.0时功能比较弱的缓存机制,HTTP/1.0存在该字段时会忽略“Expires”和“Cache-control”字段)”,如果希望该方法能拿到其他字段,就必须要在‘Access-Control-Expose-Headers’中指定

非简单请求
对服务器又特殊要求的请求,常见的如请求方式时“PUT”或"DELETE",或者请求头Content-Type字段的类型是application/json时
非简单请求的CORS请求,会在正式通信前,增加一次HTTP查询请求,称为“预检(preflight)”请求
预检其实就是做检查,检查如果通过则允许传输数据,检查不通过则不能发送真正要发送的消息
预检的请求方式:OPTIONS, 表示这个请求时用来询问的
预检的请求头信息的关键字段

Origin
表示请求来自哪个源

Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些请求方式,会在“CORS_ALLOW_METHODS”中查找,存在则通过,不存在则不允许跨域,或在“CORS_ALLOW_METHODS”中添加该请求方式

Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,会在“CORS_ALLOW_HEADERS”中查找,存在则通过,不存在则不允许跨域,或在“CORS_ALLOW_HEADERS中”添加该头信息

另外一种跨域的解决方案

JSOBPJSONP
只能发送GET请求,主要修改在前段部分,后端需要做约束修改,发jsonp请求。优势在于支持老式浏览器,以及向不支持CORS的网站请求数据。

 

相关资料参考:

Django-cors-headers:https://github.com/ottoyiu/django-cors-headers/

浏览器同源策略及其规避方法:http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

Django跨域请求:https://www.cnblogs.com/Joe1991/articles/8483770.html

跨域资源共享CORS 详解: http://www.ruanyifeng.com/blog/2016/04/cors.html