django框架进阶-解决跨域问题
#######################################
""" 一、为什么会有跨域问题? 是因为浏览器的同源策略是对ajax请求进行阻拦了,但是不是所有的请求都给做跨域,像是一般的href属性,a标签什么的都不拦截。 同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能, 如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 一个源的定义 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。 举个例子: 下表给出了相对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)
"""
#######################################
举例说明
demo1 8002端口
# urls.py urlpatterns = [ url(r'^abc/', views.abc), ] # views.py def abc(request): return HttpResponse("rion")
demo2 8000端口
# urls.py urlpatterns = [ url(r'^xyz/', views.xyz), ]
# iews.py def xyz(request): return render(request, "xyz.html")
# xyz.html <!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> $("#b1").click(function () { $.ajax({ url: "http://127.0.0.1:8002/abc/", type: "get", success:function (res) { console.log(res); } }) }); </script> </body> </html> 现在,打开使用浏览器打开http://127.0.0.1:8000/xyz/,点击页面上的 '点我' 按钮,会在console页面发现错误信息如下:
为什么报错呢?因为同源策略限制跨域发送ajax请求。
#######################################
""" 二、解决跨域问题的两种方式 JSONP CORS """
#######################################
细心点的同学应该会发现我们的demo1项目其实已经接收到了请求并返回了响应,是浏览器对非同源请求返回的结果做了拦截。
再细心点的同学会发现,我们使用cdn方式引用的jQuery文件也是跨域的,它就可以使用。
同样是从其他的站点拿东西,script标签就可以。那我们能不能利用这一点搞点事情呢?
把xyz.html中的代码改一下:
<!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 src="http://127.0.0.1:8002/abc/"></script> </body> </html>
会报错,rion is not define ,该页面上却没有定义一个名为rion的变量。所以出错了。
那我定义一个rion变量还不行吗?
把xyz.html中的代码改一下:
<!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> var rion = 100; </script> <script src="http://127.0.0.1:8002/abc/"></script> </body> </html>
这次就不会报错了。
我定义一个变量可以,那可不可以定义一个函数呢?
<!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()")
我返回的 rion(),页面上拿到这个响应之后直接执行了rion函数!
那函数中可不可以传递参数呢?我们试一下!
把xyz.html中的代码改一下:
<!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(res) { console.log(res); } </script> <script src="http://127.0.0.1:8002/abc/"></script> </body> </html>
demo1中的视图函数:
def abc(request): res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]} return HttpResponse("rion({})".format(json.dumps(res)))
果然传递参数也是可以的!
我们通过script标签的跨域特性来绕过同源策略拿到想要的数据了!!!
这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。
将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。
但是我们更多时候是希望通过事件触发数据的获取,而不是像上面一样页面一刷新就执行了,这样很不灵活。
其实这很好解决,我们可以通过javascript动态的创建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(res) { console.log(res); } function addScriptTag(src){ var scriptEle = document.createElement("script"); $(scriptEle).attr("src", src); $("body").append(scriptEle); $(scriptEle).remove(); } $("#b1").click(function () { addScriptTag("http://127.0.0.1:8002/abc/") }) </script> </body> </html>
这样当我们点击b1按钮的时候,会在页面上插入一个script标签,然后从后端获取数据。
为了实现更加灵活的调用,我们可以把客户端定义的回调函数的函数名传给服务端,服务端则会返回以该回调函数名,将获取的json数据传入这个函数完成回调。
<!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(res) { console.log(res); } function addScriptTag(src) { var scriptEle = document.createElement("script"); $(scriptEle).attr("src", src); $("body").append(scriptEle); $(scriptEle).remove(); } $("#b1").click(function () { addScriptTag("http://127.0.0.1:8002/abc/?callback=rion") }); </script> </body> </html>
view.py
def abc(request): res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]} func = request.GET.get("callback") return HttpResponse("{}({})".format(func, json.dumps(res)))
这样就能实现动态的调用了。
jQuery中有专门的方法实现jsonp。
<!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> $("#b1").click(function () { $.getJSON("http://127.0.0.1:8002/abc/?callback=?", function (res) { console.log(res); }) }); </script> </body> </html>
要注意的是在url的后面必须要有一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个?是jQuery内部自动生成的一个回调函数名。
但是如果我们想自己指定回调函数名,或者说服务上规定了回调函数名该怎么办呢?我们可以使用$.ajax方法来实现:
<!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> $("#b1").click(function () { $.ajax({ url: "http://127.0.0.1:8002/abc/", dataType: "jsonp", jsonp: "callback", jsonpCallback: "rion2" }) }); function rion2(res) { console.log(res); } </script> </body> </html>
不过我们通常都会将回调函数写在success回调中:
<!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> $("#b1").click(function () { $.ajax({ url: "http://127.0.0.1:8002/abc/", dataType: "jsonp", success: function (res) { console.log(res); } }) }) </script> </body> </html>
####################################################
""" 三、JSONP JSONP是json用来跨域的一个东西。原理是通过script标签的跨域特性来绕过同源策略。(创建一个回调函数,然后在远程服务上调用这个函数并且将json数据形式作为参数传递,完成回调)。 JSONP的缺点 jsonp有个缺陷就是只能get 而且会把请求的内容发送到url中导致安全性极低 # script标签不受同源策略的影响,手动创建一个script标签,传递URL,同时传入一个回调函数的名字 # 服务器得到名字后,返回数据时会用这个函数名来包裹住数据,客户端获取到数据之后,立即把script标签删掉 """
#######################################
CORS解决跨域问题
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而解决AJAX只能同源使用的限制。
CORS简介
CORS需要浏览器和服务器同时支持。目前基本上主流的浏览器都支持CORS。所以只要后端服务支持CORS,就能够实现跨域。
简单请求和非简单请求介绍
浏览器将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
简单请求的处理方式
在跨域场景下,当浏览器发送简单请求时,浏览器会自动在请求头中添加表明请求来源的 Origin 字段。
我们的后端程序只需要在返回的响应头中加上 Access-Control-Allow-Origin 字段,并且把该字段的值设置为 跨域请求的来源地址或简单的设置为 * 就可以了。
例如:我们可以在Django中间件中的process_response方法来给相应对象添加该字段。
from django.utils.deprecation import MiddlewareMixin class CorsMiddleware(MiddlewareMixin): def process_response(self, request, response): # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 * response['Access-Control-Allow-Origin'] = '*' return response
非简单请求的处理方式
我们开发中常用到的那些请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json的都是非简单请求。
对于非简单请求,浏览器通常都会在请求之前发送一次 OPTIONS 预检 请求。该请求会像后端服务询问是否允许从当前源发送请求并且询问允许的 请求方法 和 请求头字段。
举个例子:
我们前端使用axios向后端发送PUT请求,结果:失败,
解决办法也很简单,我们可以在后端简单的给响应对象添加上 常用请求方法(PUT、DELETE)的支持就可以了
在上面Django的中间件中添加如下代码:
from django.utils.deprecation import MiddlewareMixin class CorsMiddleware(MiddlewareMixin): def process_response(self, request, response): # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 * response['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': # 允许发送 PUT 请求 response['Access-Control-Allow-Methods'] = 'PUT, DELETE' # 允许在请求头中携带 Content-type字段,从而支持发送json数据 response['Access-Control-Allow-Headers'] = 'Content-type' return response
################################################
使用django-cors-headers 我们这个中间件确实能解决目前的CORS跨域问题,但是我们的土方法肯定是不够严谨的,已经有人造好轮子-- 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' )
################################################
{%csrf_token%}的作用 <form> {%csrf_token%} </form> 在django中我们需要在templates的form中加入{%csrf_token%}这串内容, 它的作用是当我们get表单页面时,服务器返回页面的同时也会向前端返回一串随机字符, post提交时服务器会验证这串字符来确保用户是在服务端返回的表单页面中提交的数据, 防止有人通过例如jquery脚本向某个url不断提交数据,是一种数据提交的验证机制。
################################################
""" 四、CORS跨域 随着技术的发展,现在的浏览器可以主动支持设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是设置响应头,使得浏览器允许跨域请求。 2.cors:跨域资源共享 # 使用自定义的HTTP头部允许浏览器和服务器相互通信 # 1.如果是简单请求,直接设置允许访问的域名: # 允许你的域名来获取我的数据 # response['Access-Control-Allow-Origin'] = "*" # 2.如果是复杂请求,首先会发送options请求做预检,然后再发送真正的PUT/POST....请求 # 因此如果复杂请求是PUT等请求,则服务端需要设置允许某请求 # 如果复杂请求设置了请求头,则服务端需要设置允许某请求头 #简单请求: # 一次请求 #非简单请求: # 两次请求,在发送数据之前会先发一次请求用于做“预检”, # 只有“预检”通过后才再发送一次请求用于数据传输。 """
#######################################
""" 五、JSONP和CORS的区别 JSONP:服务端不用修改,需要改前端。发jsonp请求 JSONP:只能发GET请求 CORS:前端的代码不用修改,服务端的代码需要修改。如果是简单请求的话在服务端加上一个响应头。 CORS:可以发任意请求
"""
#######################################
#######################################
#######################################
#######################################
demo1 8002端口