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

05-01 6625阅读 0评论

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

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

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

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,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 端,浏览器里,这么做的就不合适,

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

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

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

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

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

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

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

      比如:

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

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

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

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

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

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

      需注意即使两个不同的域名指向同一个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字符串. 我们在抓包中看到的就是这样的效果:

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

      在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数据包

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

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

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

      后端代码

      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)
      

      前端代码

      
      
          
          Title
          
          
              $(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)
              // }
          
      
      
        我是一个完美的html页面
          {{username}}
          {{hobby}}
        
      
      
      

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

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

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

      它的具体工作流程是:

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

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

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

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

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

      这是一次跨域请求

      请求确实发送到服务器了

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

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

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

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

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

      CORS 跨域方案简单总结:

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

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

      CORS支持GET,POST常规请求

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

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

      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里面的内容:

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

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

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

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

        简单请求:一次请求

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

      • 关于“预检”

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

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

          Access-Control-Request-Method

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

          Access-Control-Request-Headers

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

          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
          

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

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

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

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

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

          服务端axios:只响应数据

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

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

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

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

          解决办法:

          方法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}, 即可

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

          我们采用方法1

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

          可以正常显示中文了

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

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

          Edit Configurations这里编辑运行参数

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

          端口号改成8008

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

          客户端axios2: 请求数据

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

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

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

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

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

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

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

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

          如果想让客户端拿到数据,我们采取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
          

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

          此时浏览器访问

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

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

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

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

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

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

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

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

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

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

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

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


免责声明
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明。
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所
提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何
损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在
转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并白负版权等法律责任。

手机扫描二维码访问

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,6625人围观)

还没有评论,来说两句吧...

目录[+]