跨域问题及解决方案
1. 什么是跨域
-
跨域带来的风险?
-
跨域请求和Ajax技术都会极大地提高页面的体验,但同时也会带来安全的隐患,其中最主要的隐患来自于CSRF(Cross-site request forgery)跨站请求伪造。
-
csrf攻击的大致原理
假如一家银行用以运行转账操作的URL地址如下:
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置一张不可描述的图片引诱你点击,点击图片后向"http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman"发送请求
如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。
-
-
跨域是指我们访问一个网站,如:http://127.0.0.1:8000这个url,从这个页面中又去访问http://127.0.0.1:9000这个url,这个时候就引发了跨域,当域名、端口、二级域名不同都会引发跨域。 此时9000端口的服务端可以接收到请求,也会给浏览器响应数据,但是到达浏览器后就被拦截了,因为浏览器的同源策略。
URL 结果 原因 http://www.cnblogs.com/liuweida 成功 域名、协议、端口相同 https://www.cnblogs.com/liuweida 失败 协议不同 http://www.cnblogs.com:8888/liuweida 失败 端口不同 http://www.cnblogs.cn/liuweida 失败 域名不同
2. 什么是同源策略?
-
同源策略是一种约定,它是浏览器最核心也会是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。它约定请求的url地址,必须与浏览器的url地址处于同域上,也就是域名,端口,协议都相同。如果不同,就会报错:
-
实际上的结果是,请求已经被发送过去了,目标服务器也对请求做出了响应,只是浏览器对非同源请求的返回结果做了拦截。
一些内嵌资源不受限制
如:
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。<link rel="stylesheet" href="...">
标签嵌入CSS。<img>
嵌入图片。<video>
和<audio>
嵌入多媒体资源。<object>
,<embed>
和<applet>
的插件。@font-face
引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。<frame>
和<iframe>
载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。
限制范围
非同源的网站,主要有3种行为受到限制
- 无法共享 cookie, localStorage, indexDB
- 无法操作彼此的 DOM 元素
- 无法发送 Ajax 请求
3. 如何解决跨域发送请求问题
3.1 jsonp
-
jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
-
基于script标签实现的跨域
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript"> var messagetow = function(data){ alert(data); }; var url = "http://192.168.31.137/train/test/jsonpthree?callback=messagetow"; var script = document.createElement('script'); script.setAttribute('src', url); document.getElementsByTagName('body')[0].appendChild(script); </script> </head> <body> </body> </html>
-
基于jquery跨域
$.ajax({ type : 'get', url:'http://192.168.31.137/train/test/testjsonp', data : { name : name, sex : sex, address : address, looks : looks, }, cache :false, jsonp: "callback", jsonpCallback:"success", dataType : 'jsonp', success:function(data){ alert(data); }, error:function(data){ alert('error'); } });
3.2 cors[推荐]
- CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通(需要客户端和服务端协同处理)。
- CORS有两种请求,简单请求和非简单请求。
简单请求
-
什么是简单请求?
只要同时满足以下两大条件,就属于简单请求。 (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
-
简单请求实现cors
1.前端不需要进行特殊的操作 2.后端需要加入请求头Access-Control-Allow-Origin,代码如下: def get_time(request): if request.method == 'GET': ntime = time.strftime('%Y-%m-%d %X') obj = HttpResponse(ntime) obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001' return obj
复杂请求
-
两次请求,在发送数据之前会先发第一次请求做预检,只有预检通过后在发一次请求作为数据传输。
- 请求方式:OPTIONS - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息 - 如何“预检” => Access-Control-Allow-Origin(必含) 设置为'*',允许所有域名访问 一般设置为指定域名,如:'Access-Control-Allow-Origin:http://192.168.12.87' => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过 Access-Control-Request-Method => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过 Access-Control-Request-Headers
-
复杂请求实现
前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <button id="a" class="btn btn-success">go</button> <script> $("#a").click(function () { $.ajax({ url:'http://127.0.0.1:8000/money/', type:'post', contentType:'application/json', data:{"name":"yjh"}, success:function (data) { alert(data) } } ) }) </script> </body> </html>
后台
from django.http import QueryDict def get_money(request,response): if request.method == 'OPTIONS': response['Access-Control-Allow-Origin'] = '*' # 允许所有域名访问,一般会设置成指定的域名 response['Access-Control-Allow-Headers'] = 'Content-Type' # 复杂请求添加的头信息 response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001' return response
全局配置
-
全局配置可以写在中间件中,浏览器是接收到返回数据的时候出现禁用,所以在数据返回时添加响应头信息即可,所以重写中间件中process_response方法
-
步骤一:在py文件中写如下代码:
from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): if request.method=="OPTIONS": response['Access-Control-Allow-Origin'] = 'http://localhost' response["Access-Control-Allow-Headers"]="Content-Type" response["Access-Control-Allow-Origin"] = "http://localhost:8080" return response
-
步骤二:在配置文件settings.py中添加注册中间件
MIDDLEWARE = [ 'app01.my_middleware.CorsMiddleware', # 在第一行写是因为django中间件的执行顺序,process_response倒序执行 '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', ]