解决跨域的方法

解决跨域的方法

在讲跨域之前,应该先了解一下浏览器的同源策略

同源策略是浏览器之间的一种约定,是浏览器最基本和最核心的安全策略,如果没有同源策略,网站很容易受到XSS和CSRF等攻击。

同源策略就是指网站:协议+域名+端口号三者相同

在同源策略下限制的内容:cookie,localStorage,sessionStorage,ajax请求

有三个标签允许跨域加载资源

  • <img>
  • <link>
  • <srript>
常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

注意:

  1. 如果是因为协议造成的跨域(http和https),前端是解决不了的,愉快的丢给后端人员解决
  2. 在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”
解决跨域的方式
  1. JSONP

    JSONP利用了<script>可以跨域加载资源,可以从其他网站动态获得json数据。JSONP请求要请求服务器要支持才可以

    在一下常见的网站,如bilibili,爱奇艺,腾讯视频,他们首页的搜索接口都是可以支持jsonp请求的

    实例

  <!DOCTYPE html>
  <html lang="en">
  
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
  </head>
  
  <body>
      <input type="text" id="search">
      <button id="button">搜索</button>
      <script>
          (() => {
              // 获取搜索关键字
              let button = document.getElementById("button");
              let search = document.getElementById("search");
              button.onclick = function () {
                  let body = document.body
                  let word = search.value;
                  console.log(search)
                  console.log(word)
                  // 动态创建一个script标签
                  let scriptTag = document.createElement('script');
                  scriptTag.setAttribute('src',
                      `https://s.search.bilibili.com/main/suggest?func=suggest&suggest_type=accurate&sub_type=tag&main_ver=v1&highlight=&userid=3730600&bangumi_acc_num=1&special_acc_num=1&topic_acc_num=1&upuser_acc_num=3&tag_num=10&special_num=10&bangumi_num=10&upuser_num=3&term=${word}&rnd=0.428375950382754&jsonp=jsonp&callback=myCallBack`
                      )
                  document.body.appendChild(scriptTag);
              }
  
          })()
      </script>
      <script>
          function myCallBack(e) {
              console.log(e)
              // 创建一个列表
              for(let i = 0; i < e.result.tag.length; i++){
                 console.log(e.result.tag[i].value) 
              }
          }
      </script>
  </body>
  
  </html>

在这里插入图片描述

JSONP的优缺点:

  1. 优点:简单兼容性好
  2. 缺点:仅支持get请求,有局限性,可能会有XSS攻击

2.cors

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

满足以下条件的为简单请求

  1. 请求方法为get,head,post
  2. 请求头中的Content-Typetext/plain,mutipart/form-data,application/x-www-form-urlcoded
复杂请求

不属于简单请求的就是复杂请求

实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
    <script>
        $.ajax({
            method:'get',
            url: "http://localhost:3000/simpleGet",
            success: function (response) {
                console.log(response)
            }
        });
        $.ajax({
            method: "put",
            url: "http://localhost:3000/simplePut",
            success: function (response) {
                console.log(response)
            }
        });
    </script>
</body>
</html>
let express = require("express");
let app = new express();

// 配置白名单
const whiteAdress = ["http://127.0.0.1:5500"]

app.use(function(req,res,next){
    let orgin = req.headers.origin;
    // console.log(req.headers)
    if(whiteAdress.includes(orgin)){
        res.setHeader("Access-Control-Allow-Origin", orgin)
        res.setHeader('Access-Control-Allow-Methods', 'PUT')
        if(req.method == "option"){
            res.end(200,"我是简单请求");
        }
    }
    next();
})

app.get("/simpleGet", function(req, res){
    res.send("我是简单请求")
})
app.put("/simplePut",function(req,res){
    res.end("我是put请求")
})

app.listen(3000, ()=> {
    console.log("3000端口已启用")
})

在这里插入图片描述

  1. postMessage

为数不多的可以跨域操作window属性之一可以解决

  • 页面和其他新开窗口的数据传递
  • 多窗口消息传递
  • 页面嵌套iframe消息传递
  • 上面三个场景跨域数据传递

postMessage()允许来自不同源的脚本采用异步方式进行有限的通信,该方法由三个参数

  • message:发送的数据
  • targetOrigin:发送地址的URL
  • transfer:是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

实例
a.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <iframe src="http://127.0.0.1:4000/" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
    <script>
        function load(){
            let frame = document.getElementById('frame')

            frame.contentWindow.postMessage("我是a页面", "http://127.0.0.1:4000/")
            window.onmessage = function (e) { //接受返回数据
                console.log(e.data) 
            }
        }
    </script>
</body>

</html>

b页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>我是b页面</h1>
    <script>
        (() => {
            window.onmessage = function(e) {
                console.log(e.data);
                e.source.postMessage("我是b页面","http://127.0.0.1:5500/postMessage跨域a.html")
            }
        })()
    </script>
</body>
</html>

在这里插入图片描述

  1. websocket
    Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
    websocket的实例就放我以前写的一个聊天室
    多人聊天室实例

  2. Node中间件代理(两次跨域)
    实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

实例
a.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script>
      $.ajax({
        url: 'http://localhost:3000',
        type: 'post',
        data: { name: 'larmy', password: '123456' },
        contentType: 'application/json;charset=utf-8',
        success: function(result) {
          console.log(result) 
        },
        error: function(msg) {
          console.log(msg)
        }
      })
     </script>
</body>
</html>

a.js

const http = require('http')
// 第一步:接受客户端请求
const server = http.createServer((request, response) => {
  // 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  // 第二步:将请求转发给服务器
  const proxyRequest = http
    .request(
      {
        host: '127.0.0.1',
        port: 4000,
        url: '/',
        method: request.method,
        headers: request.headers
      },
      serverResponse => {
        // 第三步:收到服务器的响应
        var body = ''
        serverResponse.on('data', chunk => {
          body += chunk
        })
        serverResponse.on('end', () => {
          console.log('The data is ' + body)
          // 第四步:将响应结果转发给浏览器
          response.end(body)
        })
      }
    )
    .end()
})
server.listen(3000, () => {
  console.log('The proxyServer is running at http://localhost:3000')
})
const http = require('http')
const data = { title: 'larmy', password: '123456' }
const server = http.createServer((request, response) => {
  if (request.url === '/') {
    response.end(JSON.stringify(data))
  }
})
server.listen(4000, () => {
  console.log('The server is running at http://localhost:4000')
})

6.nginx
这个还不是很了解

7.window.name + iframe
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

实例

a页面,放在5500端口

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <iframe src="http://127.0.0.1:4000/" frameborder="1" onload="load()" id="iframe"></iframe>
    <script>
      let first = true
      // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
      function load() {
        if(first){
        // 第1次onload(跨域页)成功后,切换到同域代理页面
          let iframe = document.getElementById('iframe');
          iframe.src = 'http://127.0.0.1:5500/window.name的b页面.html';
          first = false;
        }else{
        // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
          console.log(iframe.contentWindow.name);
        }
      }
    </script>
</body>
</html>

b页面,放在5500端口

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

c页面,放在4000端口

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>c页面</h1>
    <script>
        window.name = '我是c页面'  
    </script>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值