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

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

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

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

同源策略

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

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

不同源的客户端脚本[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字符串. 我们在抓包中看到的就是这样的效果:


9b6c3b9671e04baca13f3e340aeda38e.png

在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属性值已被修改


相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
1天前
|
前端开发 API 数据安全/隐私保护
Web前端开发中的跨域资源共享(CORS)解决方案
【2月更文挑战第5天】在Web前端开发中,跨域资源共享(CORS)是一个常见的挑战。本文将探讨CORS的概念和原理,并介绍一些常用的解决方案,包括服务器端配置和前端处理方法,帮助开发者更好地应对跨域请求问题。
129 4
|
1天前
|
前端开发 开发者
前端开发中的跨域资源共享(CORS)解决方案探讨
【2月更文挑战第2天】跨域资源共享(CORS)是前端开发中常见的问题,本文将深入探讨CORS的原理及解决方案,包括简单请求、预检请求以及常用的CORS解决方案,为前端开发者提供深入的理解和应对CORS问题的有效方法。
46 1
|
1天前
|
前端开发 JavaScript API
探索前端开发中的跨域资源共享(CORS)
【2月更文挑战第3天】在前端开发中,跨域资源共享(CORS)是一个至关重要的话题。本文将深入探讨CORS的概念、工作原理以及如何在前端项目中正确配置和处理跨域请求,帮助开发者更好地理解和应用CORS技术。
30 7
|
1天前
|
前端开发 安全 JavaScript
前端开发中的跨域资源共享(CORS)机制
【2月更文挑战第3天】 在前端开发中,跨域资源共享(CORS)机制是一个重要的安全性问题。本文将介绍CORS的概念、原理和实现方式,并探讨在前端开发中如何处理跨域资源请求,以及如何提高网站的安全性。
|
1天前
|
JSON 前端开发 安全
前端开发中的跨域解决方案探究
跨域是前端开发中常见的问题之一,本文将探讨跨域的概念、产生的原因,以及常见的解决方案,包括JSONP、CORS、代理等。通过本文的学习,读者可以深入了解跨域问题及解决方案,为自己的前端开发工作提供参考。
|
1天前
|
JSON 前端开发 安全
前端开发中的跨域问题及解决方案
在前端开发中,跨域是一个常见但又令人头疼的问题。本文将深入探讨跨域产生的原因以及一些常见的解决方案,帮助开发者更好地理解和处理跨域情况。
|
1天前
|
Web App开发 移动开发 运维
跨域解决方案[前端+后端]
跨域解决方案[前端+后端]
34 0
|
1天前
|
前端开发 安全 开发者
前端开发中的跨域资源共享(CORS)问题及解决方案探讨
在前端开发中,跨域资源共享(CORS)是一个常见且重要的问题。本文将深入探讨CORS的原理、影响以及解决方案,帮助开发者更好地应对跨域请求问题。
|
1天前
|
JSON 前端开发 安全
前端解决跨域的六种方法
跨域问题是指当一个网页试图访问来自不同源(域名、协议、端口)的资源时,浏览器会出于安全考虑而限制这种访问。这是因为浏览器的同源策略防止了恶意网站获取其他网站的敏感信息。
|
1天前
|
JSON 前端开发 安全
Web前端开发中的跨域问题及解决方案
【2月更文挑战第8天】在Web前端开发中,跨域是一个常见且具有挑战性的问题。本文将深入探讨跨域产生的原因、影响以及多种解决方案,帮助开发者更好地理解和解决跨域问题。
http://www.vxiaotou.com