什么是跨域?
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
什么是同源策略?
同源策略又分为以下两种
- DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。 - 只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作
所谓同源是指,域名,协议,端口 均相同
如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,
则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
客户端JavaScript程序。虽然是一种解释性的脚本语言,JavaScript其实是无比强大的,原则上来讲它可以做任何事。
但是在能够在JavaScript脚本并不都是值得信赖的,所以浏览器必须对JavaScript的执行作相应的限制。
比如我们ajax可以随便发送一个请求就能取到数据,修改数据,那我们每个人都是黑客了。很多敏感信息就泄露。
但是对src等请求是没有限制的。
为什么我们需要跨域限制?
主要是出于安全的考虑
AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,
我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
- 用户登录了自己的银行页面 http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
- 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
- http://evil.com向http://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
- 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
- 而且由于Ajax在后台执行,用户无法感知这一过程。
DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:
- 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com。
- 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
- 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。
解决跨区:
合理的跨域访问---CORS
CORS:”跨域资源共享”(Cross-origin resource sharing),这是一个W3C标准
CORS需要浏览器和服务器同时支持。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信
CORS可以分成两种:
1、简单请求
- 请求方式:GET、POST、HEAD(注:什么是HEAD请求?HEAD请求和GET本质是一样的,但是HEAD请求不含数据,只有HTTP头部信息)
- HTTP头部信息不超过一下几种字段:
- 无自定义头部字段、
- Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
2、复杂请求
- 请求方式:PUT、DELETE
- 自定义头部字段
- 发送json格式数据
- 正式通信之前,浏览器会先发送OPTION请求,进行预检,这一次的请求称为“预检请求”
- option是什么?
- (
OPTION会
请求询问服务器,你支持哪种请求方法啊?
服务端响应支持GET,POST和OPTION等。。
) - 服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
简单请求
一个简单的请求大致如下:
HTTP方法是下列之一
HEAD
GET
POST
HTTP头信息不超出以下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type,但仅能是下列之一
application/x-www-form-urlencoded
multipart/form-data
text/plain
任何一个不满足上述要求的请求,即被认为是复杂请求。一个复杂请求不仅有包含通信内容的请求,同时也包含预请求(preflight request)。
简单请求的部分响应头及解释如下:
Access-Control-Allow-Origin(必含)- 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"。
Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)
。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,
即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。
Access-Control-Expose-Headers(可选) – 该项确定XmlHttpRequest2对象当中getResponseHeader()方法所能获得的额外信息。
通常情况下,getResponseHeader()方法只能获得如下的信息: Cache-Control Content-Language Content-Type Expires Last-Modified Pragma 当你需要访问额外的信息时,就需要在这一项当中填写并以逗号进行分隔
复杂请求
Access-Control-Request-Method
该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT、DELETE等等。
Access-Control-Request-Headers
该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。
多了一个OPTIONS的预检请求,例如
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...`
服务器需要对预检请求进行回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,
确认允许跨源请求,就可以做出回应。
> 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://api.bob.com
> Access-Control-Allow-Methods: GET, POST, PUT
> Access-Control-Allow-Headers: X-Custom-Header
> Content-Type: text/html; charset=utf-8
> 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(必含)
– 这是对预请求当中Access-Control-Request-Method的回复,
这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。
Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含)
这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而又不想在这一层做过多的判断,没关系,
事实上通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可。
Access-Control-Allow-Credentials(可选)
– 和简单请求当中作用相同。
Access-Control-Max-Age(可选)
– 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
(1)Access-Control-Allow-Methods 授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。
注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。
它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),
即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
(5)Access-Control-Allow-Origin:指定授权访问的域
response.addHeader("Access-Control-Allow-Origin", "http://192.168.56.130");
浏览器的正常请求和回应
下面是"预检"请求之后,浏览器的正常CORS请求。
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面头信息的Origin
字段是浏览器自动添加的。
下面是服务器正常的回应。
Access-Control-Allow-Origin: http://api.bob.com
> Content-Type: text/html; charset=utf-8
own:
基于django的cors跨域例子:
在服务端的设置:
def service(request):
# 你需要跨域传的数据
info = {"name": "egon", "age": 34, "prive": 200}
response = HttpResponse(json.dumps(info))
# 解决跨域问题 * 表示允许所有的跨域请求
response["Access-Control-Allow-Origin"] = "*"
# 在djiango 2.26版本中 经过我实验下面这个方法没用
# response['Access-Control-Allow-Origin'] = "http://127.0.0.1:8006/"
return response
Django 解决跨域问题
方案一:
在app下创建一个middlewares的py文件:
from django.utils.deprecation import MiddlewareMixin
class MyTest(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = '*'
return response
其他字段:
方案二:
django 使用django-cors-headers 解决跨域问题(待测试)
1. 安装
pip install django-cors-headers
2.添加到setting的app中
INSTALLED_APPS = (
...
'corsheaders',
...
)
3.添加中间件
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
4、setting下面添加下面的配置
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'*'
)
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
解决跨域问题jsonnp script简单版原理
http://127.0.0.1:8006/ 端口(我是向8007跨域请求端)
前端页面:
<button class="get_service">洗剪吹</button> </body> <script> // 跨域端口返回get_jsonp_data函数调用,将值以arg形式传进来。 function get_jsonp_function(arg) { alert(arg); console.log(typeof arg); var data = JSON.parse(arg); console.log(data); console.log(typeof data); } </script> <script> //点击向跨域端口发送一次service请求 function get_jsonp_data(url) { //创建一个script标签 var ele_svript = $("<script>"); //添加一个src属性 ele_svript.attr("src", url); ele_svript.attr("id", "jsonp"); // 添加到body文档树后面 $("body").append(ele_svript);
//将多余的文档删除 $("#jsonp").remove() } </script> <script> $(".get_service").on("click", function () { //以另一端传过来的接口?callback= 自定义我们的函数名 get_jsonp_data("http://127.0.0.1:8007/service/?callback=get_jsonp_function"); }) </script> </html>
http://127.0.0.1:8007/ 端口(我是服务端)
view视图端
from django.shortcuts import render, HttpResponse import json # Create your views here. def index(request): return render(request, "index.html") def service(request): # 你需要跨域传的数据 info = {"name": "egon", "age": 34, "prive": 200} # 前端自己创建函数名,我只要给那边一个callback接口就行 func = request.GET.get("callback") return HttpResponse("%s('%s')" % (func, json.dumps(info),))
jsonp跨域请求:
什么是jsonp?
JSON & JSONP:JSON 是一种基于文本的数据交换方式,或者叫做数据描述格式。
JSONP是资料格式JSON的一种“使用模式”,可以让网页从别的网域要资料,由于同源策略,
一般来说位于server1.example.com的网页无法与不是 server1.example.com的服务器沟通,而HTML的script元素是一个例外。
利用script元素的这个开放策略,网页可以得到从其他来源动态产生的JSON资料,而这种使用模式就是所谓的JSONP。
缺点:
1.JSONP只能实现GET请求。
2.JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
3.也就是说在浏览器支持cors的情况下 尽量使用cors方式。
JSONP高级版:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!--配置手机端适应--> <meta name="viewport" content="width=device-width,initial-scale=1"> <!--配置css文件 核心CSS样式压缩文件--> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.css"> <!--配置jQuery--> <script src="/static/bootstrap/jQuery.js"></script> <!--配置 核心Boot script JS压缩文件--> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </head> <body> <h3>Idext</h3> <button class="get_service">洗剪吹</button> </body> <script> // 跨域端口返回get_jsonp_data函数调用,将值以arg形式传进来。 function get_jsonp_function(arg) { alert(arg); console.log(typeof arg); var data = JSON.parse(arg); console.log(data); console.log(typeof data); } </script> <script> //终极模式 $(".get_service").on("click", function () { $.ajax({ url: "http://127.0.0.1:8007/service/", type: "get", dataType: "jsonp", //伪造ajax 基于script jsonpCallback:"get_jsonp_function", //服务端需要传并且客户端需要执行的函数, success: function (data) { // 数据也可以从success传进来并进行数据操作 } }) }) </script> </html>
服务端:
from django.shortcuts import render, HttpResponse import json # Create your views here. def index(request): return render(request, "index.html") def service(request): # 你需要跨域传的数据 info = {"name": "egon", "age": 34, "prive": 200} # 可以让跨区哪一步自己创建函数名,我只要给那边一个callback接口就行 func = request.GET.get("callback") return HttpResponse("%s('%s')" % (func, json.dumps(info),))
一个跨域的实战列子:
<script> //示例 $(".get_service").on("click", function () { $.ajax({ url: "http://www.jxntv.cn/data/jmd-jxtv2.html", type: "get", dataType: "jsonp", //伪造ajax 基于script jsonp: "callbacks", //需要发送伪造的服务端的函数名称 jsonpCallback:"list" ,//服务端需要并执行的函数,服务端以经定好了list所以必须要以list接受 success: function (data) { console.log(data); //数据处理 var html=""; // each 是jquery中的循环 $.each(data.data,function (index,weekday) { console.log(weekday); // {week: "周日", list: Array(19)} // 我自定义了一个p标签,并且取出了字段中week的值。 html+='<p>'+weekday.week+'<p>'; // 加等于的意思就是就是叠加。 $.each(weekday.list, function (j,show) { html+='<a href='+show.link+'>'+show.name+'</a>' }) }); //找到body添加到后面 $("body").append(html) } }) }) </script>