Django 多方式实现跨域访问

     阅读:24

一、什么是跨域

1.1 跨越介绍

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。 这里说明一下,无法跨域是浏览器对于用户安全的考虑,如果自己写个没有同源策略的浏览器,完全不用考虑跨域问题了。
域可以理解为:协议 + 域名 + 端口号

  • 在前后端不分离的项目中,前端使用ajax发起请求时,前端发起请求的域与后端定义API的域一致,故不会存在跨域问题
  • 在前后端分离的项目中,前端使用ajax或者axios发起请求,前后端各自运行在自己的域下,所以在发起请求的时候就会造成跨域,比如前端使用脚手架创建,运行在http://127.0.0.1:8080 后端运行在http://127.0.0.1:8000 端口,当前端发起请求时,因为域(端口不一致)不同,就会造成跨域,浏览器便会阻止该请求。

1.2 跨域分类

跨域请求分为2种,一种是简单请求,一种是复杂请求。

  • 简单请求
只要同时满足以下两大条件,就属于简单请求
HTTP 方法是下列之一
 	- HEAD
	- GET
	- POST 

HTTP 头信息不超出以下几种字段
	- Accept
	- Accept-Language
	- Content-Language
	- Last-Event-ID
	- Content-Type 只能是下列中的一个类型
	 		application/x-www-from-urlencoded
	    	multipart/form-data
	    	text/plain

任何一个不满足上述要求的请求,即会被认为是复杂请求
复杂请求会先发出一个预请求,我们也叫预检,OPTIONS请求

二、同源策略

浏览器只阻止表单以及ajax请求,并不会阻止src请求,所以我们的cdn,图片等src请求都可以发~~

2.1 Ajax测试

当我们向后端发起GET请求时,后端没有做跨域访问。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
	<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
	<title>跨域请求测试</title>
</head>
	
<body>
	<div id="app">
		<button  @click="Send">发送请求</button>
	</div>
</body>
<script>
	new Vue({
		el:"#app",
		methods:{
			Send(){
				axios.get('http://127.0.0.1:8000/api/test/')
				  .then(function (response) {
				    // 处理成功情况
				    console.log(response);
				  })
				  .catch(function (error) {
				    // 处理错误情况
				    console.log(error);
				  })
			}
		}
	})
</script>
</html>

浏览器给出提示。从origin 'null’访问’http://127.0.0.1:8000/api/test/'的XMLHttpRequest已经被CORS[跨域资源共享]策略阻止:没有’Access- control - allow - origin '头在请求的资源上存在。
在这里插入图片描述
我们在后端也发现,请求已经进来了,并成功返回了。所以数据被浏览器给劫持了
在这里插入图片描述

2.2 src 测试

当我们使用img script 标签的src来发起一个GET请求,会触发CORB(Cross-Origin Read Blocking )跨域读取阻塞。
当跨域请求回来的数据MIME type 同跨域标签应有的MIME 类型不匹配时,浏览器会启动 CORB 保护数据不被泄漏,被保护的数据类型只有html xmljson。很明显 <script><img>等跨域标签应有的MIME typehtmlxmljson 不一样。
在这里插入图片描述

详情可以看这篇文章 https://juejin.cn/post/6844903831373889550

三,解决跨域访问

3.1 请求方式添加Headers来解决跨域问题

3.1.1 简单请求处理----以GET请求为例

在前面的测试时,我们发现请求已经到达,响应后被浏览器拦截了,所以需要我们告诉浏览器不要拦截
我这里使用的是django作为Web后端开发框架,当我们发起GET请求时,我们在后端添加浏览器所需要对应的请求体参数Access-Control-Allow-Origin

1.在TestView视图函数添加请求参数

def TestView(request):
    data = {
        "user": "jack",
        "pwd": "123",
    }
    if request.method == 'GET':
        return HttpResponse(json.dumps(data),content_type='application/json')

4.重新启动,在此进行测试
前端代码如上,测试成功
在这里插入图片描述
注意:在进行post测试时,为了方便,我们需要注释掉django的csrf中间件,这是django自带的防止跨域请求伪造。
增加一个post方法判断

def TestView(request):
    data = {
        "user": "jack",
        "pwd": "123",
    }
    if request.method == 'GET':		# 请求方法需要全部大写
        return HttpResponse(json.dumps(data),content_type='application/json')
    if request.method == 'POST':
        return HttpResponse(json.dumps(data),content_type='application/json')
# settings.py django的配置文件下
MIDDLEWARE = [
    ......
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ......
]

详细了解csrf:https://juejin.cn/post/6844903653757698062.

3.1.2 复杂请求处理

复杂请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json

复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"(OPTIONS)请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

PUT方法测试

  1. 修改前端代码method: 'put'发送一个put请求过去。
    当发送put请求时,先送过来的是一个options请求,浏览器数据请求失败了,我们需要先在options请求的headers中添加Access-Control-Allow-Origin
    在这里插入图片描述
  2. 后端增加一个options判断,并添加Access-Control-Allow-Origin
# 在原来的视图函数中增加
    if request.method == 'OPTIONS':
        return HttpResponse(headers={"Access-Control-Allow-Origin":"*"})
    if request.method == 'PUT':
        return HttpResponse(json.dumps(data))
  1. 此时浏览器返回没有Access-Control-Allow-Methods ,表示PUT方法不被允许。我们在PUT和OPTIONS的headers中都再添加一个Access-Control-Allow-Methods ,然后前端继续发送PUT
    在这里插入图片描述
  2. 添加了Access-Control-Allow-Methods
    if request.method == 'OPTIONS':
        return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
    if request.method == 'PUT':
        return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
  1. 前端数据获取成功了
    在这里插入图片描述

JSON数据测试
前端携带一个json数据,向后端发送post请求,因为Content-Type中不包含JSON格式的数据,所以该请求为复杂请求
6. 前端携带JSON数据

Send(){
	axios({
	  method: 'post',
	  url: 'http://127.0.0.1:8000/api/test/',
	  data:{
	  	"course_id":1,
	  	"course_title":"中华上下五千年深度解说"
	  	}
	  })
	  .then(response => {
	   	console.log(response)
	  });
}
  1. 后端接收
def TestView(request):
    data = {
        "user": "jack",
        "pwd": "123",
    }
    print(request.method)
    if request.method == 'GET':
        return HttpResponse(json.dumps(data))
    if request.method == 'POST':
        return HttpResponse(json.dumps(data))
    if request.method == 'OPTIONS':
        return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
    if request.method == 'PUT':
        return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
    return HttpResponse("okokok")
  1. 此时,浏览器提示我们需要添加Access-Control-Allow-Headers
    在这里插入图片描述
  2. 我们在POST和OPTIONS中都添加上去,浏览器数据接收成功了
if request.method == 'POST':
    return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT","Access-Control-Allow-Headers":"content-type"})
if request.method == 'OPTIONS':
    return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT","Access-Control-Allow-Headers":"content-type"})

在这里插入图片描述

3.2 定义中间件来处理跨域问题

在上述的函数中,通过定义headers来处理跨域的多种情况,过于繁琐。我们可以写一个中间件来统一处理跨域问题
1.创建CorsMiddleware.py

from django.utils.deprecation import MiddlewareMixin

class CorsMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        response["Access-Control-Allow-Origin"] = "*"
        if request.method == "OPTIONS":
            response["Access-Control-Allow-Headers"] = "Content-Type"
            response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST"
        return response

2.在Settings.py 配置文件的中间件中添加

MIDDLEWARE = [
	......
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'GOMusicApi.utils.cors_middleware.CorsMiddleware',
]

3. 重新书写视图,去掉所有的headers,重启项目

# 这里使用了rest_framework 
from django.http import JsonResponse,HttpResponse
from rest_framework.views import APIView
class TestView(APIView):
    def post(self,request):
        print(request.body.decode('utf-8'))
        data = {
            "user": "jack",
            "password": "12333",
        }
        return JsonResponse(data,safe=False)

# 这里是使用了def 函数来实现返回数据
def TestView(request):
    print(request.method)
    if request.method == 'POST':
        print(request.body.decode('utf-8'))
        data = {
            "user": "jack",
            "password": "12333",
        }
        return JsonResponse(data,safe=False)
    return JsonResponse(data,safe=False)	
    # 这里return,因为先发送了options请求,故需要一个返回,不然后端会报没有返回对象的错误。

在这里插入图片描述

3.3 使用Django-cors-Headers 来处理跨域问题

1. 安装

pip install django-cors-headers

2. 在settings文件中进行配置

INSTALLED_APPS = [
 ......
 'django.contrib.staticfiles',
 'corsheaders',
]

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

3. Django-cors-Headers 配置介绍
在 Django 设置中配置中间件的行为。您必须至少设置以下三个设置之一

CORS_ALLOWED_ORIGINS	# 默认为空列表,设置允许发送http请求的域名
CORS_ALLOWED_ORIGIN_REGEXES	# 允许域名列表使用正则模式
CORS_ALLOW_ALL_ORIGINS		# 运行所有的域名访问
  • CORS_ALLOWED_ORIGINS

    CORS_ALLOWED_ORIGINS  =  [ 
        "https://example.com" , 
        "https://sub.example.com" , 
        "http://localhost:8080" , 
        "http://127.0.0.1:9000" , 
    ]
    

以前此设置称为CORS_ORIGIN_WHITELIST,它仍然作为别名使用,新名称优先 【注意是django3.2使用新名称】

  • CORS_ALLOWED_ORIGIN_REGEXES
    一个字符串列表,表示与授权进行跨站点 HTTP 请求的 Origin 匹配的正则表达式。默认为[]。当 CORS_ALLOWED_ORIGINS不切实际时很有用,例如当您有大量子域时。

    CORS_ALLOWED_ORIGIN_REGEXES  =  [ 
        r "^https://\w+\.example\.com$" , 
    ]
    

以前此设置称为CORS_ORIGIN_REGEX_WHITELIST,它仍然作为别名使用,新名称优先。【注意是django3.2使用新名称】

  • CORS_ALLOW_ALL_ORIGINS
    如果True,将允许所有来源。其他限制允许来源的设置将被忽略。默认为False。
    将此设置为True可能很危险,因为它允许任何网站向您的网站发出跨域请求。通常,您需要使用CORS_ALLOWED_ORIGINSCORS_ALLOWED_ORIGIN_REGEXES限制允许的来源列表。

以前此设置称为CORS_ORIGIN_ALLOW_ALL,它仍然作为别名使用,新名称优先。【注意是django3.2使用新名称】

这下面都是一些可选设置了,代码块中为默认值,一般默认值足够了

  • CORS_ALLOW_METHODS
    实际请求允许的 HTTP 动词列表

    CORS_ALLOW_METHODS  =  [ 
        "DELETE" , 
        "GET" , 
        "OPTIONS" , 
        "PATCH" , 
        "POST" , 
        "PUT" , 
    ]
    
  • CORS_ALLOW_HEADERS
    发出实际请求时可以使用的非标准 HTTP 标头列表。

    CORS_ALLOW_HEADERS  =  [ 
        "accept" , 
        "accept-encoding" , 
        "authorization" , 
        "content-type" , 
        "dnt" , 
        "origin" , 
        "user-agent" , 
        "x-csrftoken" , 
        "x-requested-with" , 
    ]
    
    • CORS_ALLOW_CREDENTIALS
      如果为True,cookie 将被允许包含在跨站点 HTTP 请求中。默认为False。

注意:在 Django 2.1 中添加了SESSION_COOKIE_SAMESITE设置,默认设置为 “Lax”,这将阻止 Django 的会话 cookie 跨域发送。将其更改为无以绕过此安全限制。

详情链接:https://pypi.org/project/django-cors-headers/

4. 我的配置

# --------------------------------------- 跨域请求配置 cors ---------------------------------------#
CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
# CORS_ORIGIN_ALLOW_ALL = True   # 允许所有源访问
CORS_ORIGIN_WHITELIST = (       #设置白名单       CORS_ALLOWED_ORIGINS【django3.2】
    'http://127.0.0.1:8000',
    'http://localhost:8000',
    'http://192.168.1.107:8080'
)

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',
)

配置成功后,前端可以成功访问后端。

3.4 总结

浏览器对不同域之间做出的行为,对应不同的请求(简单请求和复杂请求)做出了不同的拦截方式,也就解释了为什么要添加请求参数。
Access-Control-Allow-Origin :为了解决域不一致的问题;
Access-Control-Allow-Methods :为了解决PUT,DELETE等方法
Access-Control-Allow-Headers:为了解决JSON数据传输的问题