浏览器的同源策略及跨域解决方案
同源策略
一个源的定义
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
举个例子:
下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://a.xyz.com/dir2/other.html |
成功 | |
http://a.xyz.com/dir/inner/another.html |
成功 | |
https://a.xyz.com/secure.html |
失败 | 不同协议 ( https和http ) |
http://a.xyz.com:81/dir/etc.html |
失败 | 不同端口 ( 81和80) |
http://a.opq.com/dir/other.html |
失败 | 不同域名 ( xyz和opq) |
同源策略是什么
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
不受同源策略限制的
1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等。
JSONP解决跨域问题
1、JSONP原理分析
利用 浏览器加载静态资源的时候不限制跨域
<script src=""></script>
我们使用cdn方式引用的jQuery文件也是跨域的,它就可以使用。
同样是从其他的站点拿东西,script标签就可以。那我们能不能利用这一点搞点事情呢?
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>xyz</title> </head> <body> <button id="b1">点我</button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> function rion() { console.log("选我不后悔!"); } </script> <script src="http://127.0.0.1:8002/abc/"></script> </body> </html>
后端返回响应
def abc(request): return HttpResponse("rion()")
刷新页面,可以看到下面的结果。
这样就利用了浏览器加载静态资源的时候不限制跨域的特性,实现了跨域请求拿数据。
2.jQuery中的getJSON方法
jQuery中封装了专门的方法实现jsonp。
示例:
点击b1按钮发送jsonp请求到后端,然后后端返回数据。
html :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="b1">点我发送请求</button> <script src="jquery.min.js"></script> <script> // 点击b1按钮发送jsonp请求 $('#b1').click(function () { // 使用jquery提供的getJSON方法请求获取数据, // callback=? 是固定格式,会创建一个随机的字符串作为callback的value。 $.getJSON('http://127.0.0.1:8000/index/?callback=?',function (data) { console.log(data) }) }) </script> </body> </html>
后端项目函数:
def index(request): data = {"name": "zwq", "age": 18} func_name = request.GET.get('callback') print(func_name) # jQuery3310589826002995957_1547112594072 return HttpResponse('{}({})'.format(func_name,json.dumps(data)))
但JSONP有一定缺陷:
1.前后端都要支持
2.只能发GET请求
CORS解决跨域问题
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而解决AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前基本上主流的浏览器都支持CORS。所以只要后端服务支持CORS,就能够实现跨域。
1.简单请求和非简单请求介绍
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
一个请求需要同时满足以下两大条件才属于简单请求。
(1) 请求方法是以下三种方法之一: HEAD GET POST (2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
2.简单请求的处理方式
在跨域场景下,当浏览器发送简单请求时,浏览器会自动在请求头中添加表明请求来源的 Origin 字段。
我们的后端程序只需要在返回的响应头中加上 Access-Control-Allow-Origin 字段,并且把该字段的值设置为 跨域请求的来源地址或简单的设置为 * 就可以了。
例如:我们可以在Django中间件中的process_response方法来给相应对象添加该字段。
from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): # 处理简单请求的跨域请求 def process_response(self, request, response): # 告诉浏览器来自这个源的请求是我允许的跨域请求 # response['Access-Control-Allow-Origin'] = 'http://localhost:63342' # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 *,表示所有跨域简单请求都放行 response['Access-Control-Allow-Origin'] = '*' return response
非简单请求的处理方式
我们开发中常用到的那些请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json的都是非简单请求。
对于非简单请求,浏览器通常都会在请求之前发送一次 OPTIONS 预检 请求。该请求会像后端服务询问是否允许从当前源发送请求并且询问允许的 请求方法 和 请求头字段。
举个例子:
我们前端使用axios向后端发送PUT请求,结果:
看看发送的具体请求:
解决办法也很简单,我们可以在后端简单的给响应对象添加上 常用请求方法(PUT、DELETE)的支持就可以了。
在上面Django的中间件中添加如下代码:
class CORSMiddleware(MiddlewareMixin): """对所有跨域请求做放行处理""" def process_response(self, request, response): # 所有跨域简单请求都放行 response['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': # 告诉浏览器你发的非简单请求(修改了Content-type)我允许了 response['Access-Control-Allow-Headers'] = 'Content-type' # 告诉浏览器 你发送PUT请求我也支持 response['Access-Control-Allow-Methods'] = 'PUT, DELETE' return response
使用django-cors-headers
我们这个中间件确实能解决目前的CORS跨域问题,但是我们的方法肯定是不够严谨的,django有一个第三方包django-cors-headers。
安装:
pip install django-cors-headers
注册APP:
INSTALLED_APPS = [ ... 'app01.apps.App01Config', 'corsheaders', # 将 corsheaders 这个APP注册 ]
添加中间件:
必须放在最前面,因为要先解决跨域的问题。只有允许跨域请求,后续的中间件才会正常执行。
MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 添加中间件 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
配置
可以选择不限制跨域访问
CORS_ORIGIN_ALLOW_ALL = True
或者可以选择设置允许访问的白名单
CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ( # '<YOUR_DOMAIN>[:PORT]', '127.0.0.1:8080' )
更多详细配置详细请查看django-cors-headers项目