详细剖析让前端头疼的跨域问题是怎么产生的,又该如何解决

CSDN 2024-06-29 13:03:05 阅读 93

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN新星创作者,51CTO博客专家等。

🏆《博客》:Python全栈,前后端开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,linux,shell脚本等实操经验,网站搭建,面试宝典等分享。

所属的专栏:前端零基础,实战进阶教学

景天的主页:景天科技苑

文章目录

同源策略实现跨域请求的几种方法(1)利用jsonp进行跨域(2)利用cors进行跨域请求(3)proxy代理模式进行跨域

同源策略

同源策略,是浏览器为了保护用户信息安全的一种安全机制。

所谓的同源就是指代通信的两个地址(例如服务端接口地址与浏览器客户端页面地址)之间比较,是否协议、域名和端口相同。

不同源的客户端脚本[javascript]在没有明确授权的情况下,没有权限读写对方信息。

同源策略机制(Same Origin Policy,SOP)是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略。

则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础上的,浏览器只是针对同源策略的一种实现。

当一个浏览器的两个tab页中分别打开百度和谷歌的页面时,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。

如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以google.com下的js脚本采用ajax读取baidu.com里面的文件数据是会报错的。

跨域请求,首先浏览器为了安全,做了一个同源策略机制,也就是所谓的同源安全策略,本质上,其实是不存在所谓的跨不跨域的,

把浏览器想象成一个发送网络请求的软件,按照道理来说,请求都是可以发送出去的,但是在 web 端,浏览器里,这么做的就不合适,

如果网络上的接口可以不受限制的被任意人调用,那将是一个非常混乱的场景,所以,为了防止这种情况,浏览器做了这个同源策略来防止这种情况发生。

在这里插入图片描述

总结:协议相同+域名相同+端口号相同,浏览器才认为是同一个网站,才不会受到同源策略的影响,才可以正常的发送Ajax请求。对于 ,

所谓的同源策略是浏览器实现的,而和后台服务器无关,A 发送请求到 B. 请求实际上是发送到了 B 后台, B后台接受到数据后。

其实也有返回,只不过,这个数据返回到浏览器之后,浏览器把这个数据给劫持了,不让A网站使用

既然跨域这么麻烦,为什么要进行跨域?

因为当一个项目变大时,把所有的内容都丢在一个网站或者是后台服务器中是不现实的.

比如:

一个网站体量很大.有很多可以独立且复杂的业务

比如有一个订单管理的后台数据API网站服务.

比如有一个用户管理的后台数据API网站服务.

比如有一个新闻管理的后台数据API网站服务.

最后剩下的就是web的UI层面的东西,把这个UI层面的东西和哪个数据服务API的网站集成在一起比较合适呢?

在这里插入图片描述

需注意即使两个不同的域名指向同一个IP地址,也是跨域

实现跨域请求的几种方法

(1)利用jsonp进行跨域

jsonp 跨域是里用在浏览器中,下载一个script标签是不受同源策略限制的特性.

具体实现原理大概分下面几步

客户端动态的创建一个 script 标签

设置 script 标签的src 为跨域的服务器后台.

src 中需要带一个 callback 查询字符串,告诉后台,前端提供的方法名是什么.

然后后端,直接返回一一串 callback(data) 的字符串给浏览器即可.

jsonp使用总结:

利用 script 标签异步加载,而非传统的ajax

异步 script 加载不受浏览器同源策略限制.

给script.src=callback=fn来告知请求的后台前端提供的fn是什么

基本就是前端提供方法,后端提供数据并调用前端的这个方法.

jsonp 只支持 get 请求.(本质上,不就是下载文件吗?下载文件一般都是get请求)

jsonp的逻辑是. 在发送请求的时候. 带上一个callback字符串. 该字符串自动发送给服务器. 服务器返回数据的时候. 会带上该callback字符串. 我们在抓包中看到的就是这样的效果:

在这里插入图片描述

在Python中. 接下来, 我们还原一下该效果.

首先, 在flask中. 必须接收到前端返回的callback, 然后在返回数据的时候. 需要用前端返回的callback字符串. 将数据包裹

@app.route("/process_jsonp", methods=["GET"])

def process_jsonp():

# 获取回调字符串

cb = request.args.get("cb")

print(cb)

data = {

"name": "alex",

"age": 18

}

import json

# 用回调字符串将真实要返回的数据包裹起来

# 如果不包裹起來。前端ajax中的success将无法获取到数据

return cb + "("+json.dumps(data)+")"

在发送ajax的时候. 需要指定dataType为jsonp, 以及自由配置回调函数的参数名

$(function(){

$.ajax({

url: "/process_jsonp",

method:"get",

// 典型, 京东.

dataType: "jsonp", // 它的执行逻辑是. 请求服务上的一个js. 然后会自动执行该js.将js函数内的东西. 丢给success

jsonp:"cb", // 传递给服务器的时候. 自动带上cb=xxxxxx 服务器端接收cb即可

success: function(data){ // 此时data可以直接收取到数据

console.log(data);

}

})

})

如果我们向下拉滚动条,确定数据有刷新,但是我们看XHR数据包为空,此时我们查看JS数据包

在这里插入图片描述

在js数据包里面我们查看preview,可以看到jsonp响应数据

在这里插入图片描述

后端代码

from flask import Flask, render_template, request, make_response

import json

# 创建一个flask对象. 参数实际上是一个文件夹

server = Flask('jinghao')

# 你要处理的是哪个请求,写路由

# http://127.0.0.1:5000/

@server.route('/')

def gen(): # 通过网路请求 -> flask -> / -> gen()

print("我是一个普通函数")

dic = {

"name": "wusir",

"hobby": "洗脚"

}

# # 后端(服务器端渲染) , 数据怼到html, 发生在???????

# f = open("templates/index.html", mode="r", encoding="utf-8")

# c = f.read().replace("{ {username}}", dic['name'])

# c = c.replace("{ {hobby}}", dic['hobby'])

# 下面的逻辑和上面一致

# render_template(文件, { {变量}}=值)

#页面渲染,自动到项目目录下templates下的html文件

return render_template("index.html", username=dic['name'], hobby=dic['hobby']) # 返回的时候, 数据已经在html中了

@server.route('/hehehehehe',methods = ['post','get'])

def hehehe():

sth = request.form.get('xxm')

print('form data,',sth)

#返回json字符串

# return json.dumps({'name':'景浩','age':18},ensure_ascii=False)

#后端返回给前端一段js代码,运行后调用success中的函数

cb = request.args.get('callback')

print('回调函数名:',cb)

return cb +'('+json.dumps({ 'name':'景浩','age':18},ensure_ascii=False) + ')'

if __name__ == '__main__':

# 让服务器跑起来

# debug=True 修改了代码. 它自动重启服务

# 有时候不管用. 依然需要手动重启

server.run(debug=True)

前端代码

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Title</title>

<script src="/static/jquery.min.js"></script>

<script>

$(function(){

$("#btn").click(function(){

console.log(123);

// 访问服务器.加载数据

// js里发送请求 -> ajax

// $.get() $.post()

// 下面这用的多

$.ajax({

url: "/hehehehehe",

method: "post", // post

data: { // json.dumps()

"xxm": "meishaqubie"

},

dataType: 'jsonp', // 加上这个东西. 此时该请求就是一个jsonp

// 在抓包中能看到url的后面加上了

//callback=jQuery19103699368267737122_1694573801081

// 当响应成功回来之后. 注意, jsonp不会自动的去执行success

// 而是去执行callback对应的那个函数jQuery19103699368267737122_1694573801081(data)

// success:访问成功之后自动回调该函数

success: function(data){ // 这里的参数,是返回的数据

console.log(data); // 使用jsonp。默认的这玩意.拿不到数据

}

})

})

})

//正常jsonp这样才能调用success里面的方法,并把参数data传进去,但是callback对应的方法名貌似个带有时间戳的方法名。

// 很多,很随机。故无法在前端实现

// function jQuery19103699368267737122_1694573801081(data){

// success(data)

// }

</script>

</head>

<body>

我是一个完美的html页面

<span>{ {username}}</span>

<span>{ {hobby}}</span>

<input type="button" value="你敢点我????" id="btn">

</body>

</html>

(2)利用cors进行跨域请求

cors 是 Cross-Origin-Resource-Sharing 的缩写,也是现代浏览器普遍支持的一种非常方便的跨域方案. 跨域资源共享

CORS,全称Cross-Origin Resource Sharing ,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制

它的具体工作流程是:

浏览器在检测到你发送的 ajax 请求不是同源请求时,会自动在 http 的头部添加一个 origin 字段.

在这里插入图片描述

我们拿不到数据是因为浏览器在中间做了一层劫持。服务器的响应头里面没有设置 Access-Control-Allow-Origin 。所以数据被浏览器劫持了,无法获取

在这里插入图片描述

跨域获取不到数据的原因很简单:

这是一次跨域请求

请求确实发送到服务器了

服务器也确实把数据返回到了浏览器

但是服务器返回的响应头里面,没有告诉浏览器哪个域名可以访问这些数据,(也就是没有设置Access-Control-Allow-Origin )

于是浏览器就把这个数据丢弃了,我们也就无法获取到这个数据。

这个时候,只需要后台在相应头里加上一个 Access-Control-Allow-Origin:* 即可完成跨域数据获取.

在这里插入图片描述

CORS 跨域方案简单总结:

当浏览器发送一个请求是跨域的时候,请求头会自动加上Origin

需要后台配合设置响应头Access-Control-Allow-Origin:*|Origin即可完成跨域.

CORS支持GET,POST常规请求

同时也支持PUT,DELETE等非post,get的请求,但会先发出一次预检请求。

在这里插入图片描述

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。

浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:(也就是说如果你的请求方法是什么put、delete等肯定是非简单请求)

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,也就是说,如果你发送的application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是urlencoded的。

凡是不同时满足上面两个条件,就属于非简单请求。

我们改一下上一节的s1项目的index.html文件中的ajax里面的内容:

在这里插入图片描述

在这里插入图片描述

浏览器对这两种请求的处理,是不一样的。

简单请求和非简单请求的区别?

简单请求:一次请求

非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。 对于复杂请求,先发送options预检请求

关于“预检”

请求方式:OPTIONS“预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息如何“预检”

=> 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过

Access-Control-Request-Method

=> 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过

Access-Control-Request-Headers

在这里插入图片描述

from django.shortcuts import render

from django.http import JsonResponse

# Create your views here.

def books(request):

# return JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)

obj = JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)

# obj["Access-Control-Allow-Origin"] = "*"

obj["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000"

print(request.method)

#处理预检的options请求,这个预检的响应,我们需要在响应头里面加上下面的内容

if request.method == 'OPTIONS':

# obj['Access-Control-Allow-Headers'] = "Content-Type" #"Content-Type",首字母小写也行

# obj['Access-Control-Allow-Headers'] = "content-type" #"Content-Type",首字母小写也行。这个content-type的意思是,什么样的请求体类型数据都可以,我们前面说了content-type等于application/json时,是复杂请求,复杂请求先进行预检,预检的响应中我们加上这个,就是告诉浏览器,不要拦截

obj['Access-Control-Allow-Headers'] = "content-type,b" #发送来的请求里面的请求头里面的内容可以定义多个,后端需要将头配置上才能访问

return obj

支持跨域,简单请求

服务器设置响应头:Access-Control-Allow-Origin = ‘域名’ 或 ‘*’

支持跨域,复杂请求 分两种形式,第一种是方法不属于简单请求,另一种是请求头超过了简单请求的请求头字段

由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method

“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers

(3)proxy代理模式进行跨域

核心思想:让前端请求我们自己的后台,让后台去请求另一个后台,(因为后台和后台之间不用跨域,也没有同源策略机制,)获取真实的数据,然后把数据返回给前台,

实现方式可以利用nginx做反向代理,以及我们写一个后台中转的服务器。

这个proxy代理跨域是后端的实现,目前我只掌握了它的原理思路。具体的操作还没学会。

如上面我们做的案例,没请求拿到数据,就是同源机制,被浏览器拦截了

如果想让请求者拿到数据,响应者要告诉浏览器,让请求者拿到数据。让浏览器正常放行,这就是跨域访问

跨域(跨源)方案之CORS

CORS是一个W3C标准,全称是"跨域资源共享",它允许浏览器向跨源的后端服务器发出ajax请求,从而克服了AJAX只能同源使用的限制。

​实现CORS主要依靠后端服务器中响应数据中设置响应头信息返回的。

django的视图

def data(request):

info = { 'name': '世元', 'age': 18}

ret = JsonResponse(info)

# ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000' #允许该地址的请求正常获取响应数据

# ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000,http://127.0.0.1:8002'

ret['Access-Control-Allow-Origin'] = '*' #允许所有地址请求都能拿到数据

return ret

在这里插入图片描述

自己创建两个Django项目,分析同源问题

在这里插入图片描述

在这里插入图片描述

axios1作为服务端,axios2作为客户端

服务端axios:只响应数据

在这里插入图片描述

响应json数据,这样响应默认中文在浏览器展示会是乱码。这是由于json序列化时,默认只能显示ASSIC ii字符,如果不设置ensure_ascii=False 默认中文以unicode的编码显示

在这里插入图片描述

在这里插入图片描述

解决办法:

方法1:直接加这一句即可

, json_dumps_params={‘ensure_ascii’: False}

return JsonResponse({‘user’: ‘王’, ‘password’: 123456}, json_dumps_params={‘ensure_ascii’: False})

方法2:修改源码,原码默认序列化时只显示ASSIC ii码。我们可以修改jsonresponse原码。一般我们使用方法1

进入JsonResponse源码

将json_dumps_params=None,

修改为 json_dumps_params={‘ensure_ascii’:False}, 即可

在这里插入图片描述

我们采用方法1

在这里插入图片描述

可以正常显示中文了

在这里插入图片描述

运行axios服务端的时候,改下端口号,前后端两个网站不同源

Edit Configurations这里编辑运行参数

在这里插入图片描述

端口号改成8008

在这里插入图片描述

客户端axios2: 请求数据

在这里插入图片描述

在这里插入图片描述

axios获取不同网站信息,使用绝对路径

在这里插入图片描述

同源策略限制,没获取到数据

在这里插入图片描述

我们看得到请求头包含Origin,说明请求不是同源请求,请求跨域

在这里插入图片描述

如果想让客户端拿到数据,我们采取CORS(跨域资源共享)方案,在服务端设置响应头 Access-Control-Allow-Origin

def data(request):

info = { 'name':'景浩','age':18}

ret = JsonResponse(info,json_dumps_params={ 'ensure_ascii': False})

ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000' #注意,填写指定网站时,网站后面不能 / 等路径

# ret['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000,http://127.0.0.1:8002' #允许多个网站访问,逗号隔开

ret['Access-Control-Allow-Origin'] = '*' #允许所有地址请求都能拿到数据

return ret

在这里插入图片描述

此时浏览器访问

在这里插入图片描述

使用this的时候,要是不知道是谁,就打印一下

在这里插入图片描述

在这里插入图片描述

由打印可知,shuju这个变量以获取到值,但是由于axios2服务使用了Django的模板渲染,导致变量取值只能从后端render那里发过来,axios没发挥作用

由于后端没传值,所以一直是空的

在这里插入图片描述

axios2视图函数处,这里传值了,前端html才能取到值

在这里插入图片描述

在这里插入图片描述

有上面打印可知,看html,vue中的shuju属性值已被修改

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。