一:跨域请求
0.跨域问题的出现
问题出现:前后端来自同一个IP的不同端口
一种奇葩的解决方法:开发的时候前后端分离,部署的时候不分离
1.同源策略
① 简介
同源策略,是浏览器为了保护用户信息安全的一种安全机制
所谓的同源就是指代通信的两个地址(例如服务端接口地址与浏览器客户端页面地址)之间比较,是否协议、域名(IP)和端口相同
不同源的客户端脚本[javascript]
在没有得到服务端的明确授权的情况下,浏览器会拒绝显示服务端信息提供给前端ajax/axios
前端地址:http://www.xuexianqi.top/index.html | 是否同源 | 原因 |
---|---|---|
http://www.xuexianqi.top/user/login.html |
是 | 协议、域名、端口相同 |
http://www.xuexianqi.top/about.html |
是 | 协议、域名、端口相同 |
https://www.xuexianqi.top:443/user/login.html |
否 | 协议不同 ( https和http ) |
http:/www.xuexianqi.top:5000/user/login.html |
否 | 端口 不同( 5000和80) |
http://doc.xuexianqi.top/user/login.html |
否 | 域名不同 ( doc和www ) |
http://doc.xuexianqi.com/user/login.html |
否 | 域名不同 ( com和top ) |
2.CORS(跨域资源共享)简介
CORS是一个W3C标准,全称是"跨域资源共享",它允许浏览器向跨源的后端服务器发出ajax请求,从而克服了AJAX只能同源使用的限制。
实现CORS主要依靠后端服务器中响应数据中设置响应头信息返回的
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信
容易混淆的点:
- CORS:跨域资源共享
- CSRF:跨站请求伪造
- XSS:跨站脚本攻击
3.CORS基本流程
- 浏览器将CORS请求分成
两类
:简单请求(simple request)和非简单请求(not-so-simple request) - 浏览器发出CORS简单请求,只需要在头信息之中增加一个Origin字段
- 浏览器发出CORS非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)
- 览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段
- 只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
4.解决跨域问题的3种方法
- 前端解决(通过代理解决)
- 自己解决(自己写代码)
- 借助第三方模块(django-cors-headers)
5.CORS两种请求详解
只要同时满足以下两大条件,就属于简单请求
① 请求方法是以下三种方法之一
- HEAD
- GET
- POST
② HTTP的头信息不超出以下几种字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求
6.浏览器对这两种请求的处理,是不一样的
简单请求和非简单请求的区别
- 简单请求:发
1
次请求 - 非简单请求:发
2
次请求,在发送数据之前会先发1次请求用于做“预检
”看后端是否允许,只有“预检”通过后
才再发送1次请求用于数据传输。
请求方式:OPTIONS
预检:
检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
如何预检:
如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
- Access-Control-Request-Method
如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
- Access-Control-Request-Headers
二:解决跨域问题(服务端)
简单请求
1.原Django项目:apps/user/views.py
from django.http import JsonResponse def test(request): obj = JsonResponse({'name': 'Darker', 'age': '18'}) # 值针对简单请求 obj['Access-Control-Allow-Origin'] = '*' # 允许所有IP访问 return obj
2.原Django项目:apps/user/urls.py
from django.urls import path from user import views urlpatterns = [ path('test/', views.test), ]
3.原Django项目:dev.py中注释掉CSRF
4.再创建1个Django项目(用另外的端口)
① templates中创建index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <body> <button id="btn">点我</button> </body> <script> $('#btn').click(function () { $.ajax({ url: 'http://127.0.0.1:8000/user/test/', method: 'get', success: function (data) { console.log(data) } }) }) </script> </html>
② views.py
from django.shortcuts import render def index(request): return render(request, 'index.html')
③ urls.py
from django.urls import path from app01 import views urlpatterns = [ path('test/', views.index), ]
非简单请求
原Django项目apps/user/views.py
from django.http import JsonResponse def test(request): obj = JsonResponse({'name': 'Darker', 'age': '18'}) if request.method == 'OPTIONS': obj['Access-Control-Allow-Headers'] = 'Content-Type,authorization' # 或者填写 * obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8002' # 8002端口是当前项目的 return obj
新Django项目templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <body> <button id="btn">点我</button> </body> <script> $('#btn').click(function () { let obj = {name: 'Darker'} $.ajax({ url: 'http://127.0.0.1:8000/user/test/', method: 'post', contentType: 'application/json', headers: {authorization: 'Darker'}, data: JSON.stringify(obj), success: function (data) { console.log(data) } }) }) </script> </html>
3.中间件处理
① 在原Django项目的根路径创建mymiddle.py
- 自定义中间件
from django.utils.deprecation import MiddlewareMixin class CoreMiddle(MiddlewareMixin): def process_response(self, request, response): if request.method == 'OPTIONS': response['Access-Control-Allow-Headers'] = 'Content-Type, authorization' # 如果是 * 就代表全部IP都可以访问 response['Access-Control-Allow-Origin'] = '*' return response
② 在原Django项目的dev.py
的中间件中添加自定义中间件
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'mymiddle.CoreMiddle', # 这一句 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
③ 原Django项目apps/user/views
中替换成如下代码
from django.http import JsonResponse def test(request): return JsonResponse({'name': 'Darker', 'age': '18'})
三:解决跨域问题(第三方)
1.后端配置
① 后端安装跨域模块
pip install django-cors-headers
② 到dev.py
中进行注册
INSTALLED_APPS = ( ... 'corsheaders' )
③ 到dev.py
中添加中间件
MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', # 这个是原本就存在的 ... ]
④ 到dev.py
中添加如下代码
CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True # CORS_ORIGIN_WHITELIST = ( # 'http://127.0.0.1:8080', # ) CORS_ALLOWED_ORIGINS_REGEXES=[ r'^http://.*?$', ] # CORS_ORIGIN_REGEXES_WHITELIST = ( # r'^http://.*?$', # ) 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', )
⑤ 设置dev.py
的ALLOWED_HOSTS
ALLOWED_HOSTS = ['*']
2.后端测试
① (测试 - 后台)到apps/user/views
中替换成如下代码
from django.http import JsonResponse def test(request): return JsonResponse({'name': 'Darker', 'age': '18'})
② (测试 - 后台)到apps/user/urls
中替换成如下代码
from django.urls import path from user import views urlpatterns = [ path('test/', views.test), ]
③ 启动项目
python manage.py runserver 127.0.0.1:8000
④ 访问测试
3.前端测试
① (测试 - 前台)App.vue 中换成如下代码
<template> <div id="app"> <router-view/> {{name}} </div> </template> <script> export default { data () { return { name: [] } }, mounted () { this.$axios.get(this.$settings.base_url + '/user/test/').then(res => { this.name = res.data }) } } </script>
② 启动项目
npm run serve
4.测试效果
四:解决跨域问题(前端)
1.前端App.vue
<template> <div id="home"> <h1>我是主页</h1> <h2>{{info}}</h2> </div> </template> <script> export default { name: 'Home', data () { return { info: [] } }, mounted () { this.$axios.get('/moreClassicList?sortId=1&showType=3').then(res => { console.log(res.data) }) } } </script> <style scoped> </style>
2.前端项目根路径的vue.config.js
const webpack = require("webpack"); module.exports = { configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery", "window.$": "jquery", Popper: ["popper.js", "default"] }) ] }, devServer: { proxy: { '/ajax': { target: 'https://m.maoyan.com/', changeOrigin: true }, '/user': { target: 'http://127.0.0.1:8000', changeOrigin: true } } } };