一:跨域请求
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
}
}
}
};