同源策略及跨域方式

同源策略

  • 同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源
  • 同源策略 SOP(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,其初衷是为了浏览器的安全性,通过以下三种限制,保证浏览器不易受到 XSS、CSFR 等攻击。
  • 同源策略限制了以下行为:
    • (1)Cookie、LocalStorage 和 IndexDB 无法读取
    • (2)DOM 和 Js 对象无法获得
    • (3)AJAX 请求不能发送
  • 同源策略请求:ajax/fetch
  • 例:下面哪些不符合同源策略:(ABCDEF)
    • A、http://www.domain.com/a.jshttp://www.domain.com/lab/c.js
      • 同一域名,不同文件或路径 允许通信
    • B、http://www.domain.com:8000/a.jshttp://www.domain.com/b.js
      • 同一域名,不同端口 不允许通信
    • C、http://www.domain.com/a.jshttps://www.domain.com/b.js
      • 同一域名,不同协议 不允许通信
    • D、http://www.domain.com/a.jshttp://domain.com/c.js
      • //主域相同,子域不同 不允许通信
    • E、http://www.domain1.com/a.jshttp://www.domain2.com/b.js
      • 不同域名 不允许通信
    • F、http://www.domain.com/a.jshttp://192.168.4.12/b.js
      • 域名和域名对应相同 ip 不允许通信

跨域(非同源策略请求)

  • 出于浏览器的同源策略限制,浏览器会拒绝跨域请求。(严格的说,浏览器并不是拒绝所有的跨域请求,实际上拒绝的是跨域的读操作。)
  • 浏览器的同源限制策略是这样执行的:
    • 通常浏览器允许进行跨域写操作(Cross-origin writes),如链接,重定向
    • 通常浏览器允许跨域资源嵌入(Cross-origin embedding),如 img、script 标签
    • 通常浏览器不允许跨域读操作(Cross-origin reads)
  • 其他跨域情况:调用第三方平台的数据接口(跨域请求方式情况可能比同源更多)

跨域处理方式

1. JSONP

  • script

  • img

  • link

  • iframe


  • =>不存在跨域请求的限制,同源跨域均能拿到

    <!-- jsonp.html -->
    <!-- 跨域 成功-->
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <!-- 同源 成功 -->
    <script src="./1.jsonp.js"></script
    
  • 原理图 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRTbWEKa-1573484898000)(./assets/kyjsonp.png)]

    //客户端
    $.ajax({
      url: "http://127.0.0.1:8001/list",
      method: "get",
      dataType: "jsonp", //=>执行的是JSONP的请求
      success: res => {
        console.log(res);
      }
    });
    
    //服务器 node 文件名 执行
    let express = require("express"),
      app = express(); //引入并执行
    app.listen(8001, () => {
      console.log("OK!");
    }); //监听端口8001
    app.get("/list", (req, res) => {
      let { callback = Function.prototype } = req.query; //若callback为空执行异名空函数,若非空拿到func()
      let data = { code: 0, message: "hhh" };
      res.send(`${callback}(${JSON.stringify(data)})`);
    });
    
    • 优点:兼容性好(兼容低版本 IE)
    • 缺点:
      1. JSONP 只支持 GET 请求(url 劫持不安全)
      2. XMLHttpRequest 相对于 JSONP 有着更好的错误处理机制

2. CORS 跨域资源共享

// 客户端正常发请求
<script src="../node_modules/axios/dist/axios.js"></script>
<script>
    axios.get('http://127.0.0.1:3001/user/list')
    .then(result=>{
        console.log(result);
    })
</script>
// 服务端
/* sever.js */
/*-MIDDLE MARE- 中间件*/
    app.use((reg,res,next)=>{
    const{
        ALLOW_ORIGIN,
        CREDENTIALS,
        HEADERS,
        ALLOW_METHODS
        }=CONFIG.CROS;
    res.header('Access-Control-Allow-Origin',ALLOW_ORIGIN);//允许头
    res.header('Access-Control-Allow-Credentials',CREDENTIALS);
    res.header('Access-Control-Allow-Headers',HEADERS);
    res.header('Access-Control-Allow-Methods',ALLOW_METHODS);
    req.method==='OPTIONS'?res.send('CURRENT SERVICE SUPPORT CROSS DOMAIN'});//真正发送之前发送的试探性请求,有结果再走真正的正式请求
    app.use(session(CONFIG.SESSION));
    app.use(bodyParser.urlencoded({
    extended:false
    ]));
/* config.js */
    module.exports={
        //=>WEB服务端口号
        PORT3001//=>CROS跨域相关信息
        CORS{
            ALLOW_ORIGIN:'http://127.0.0.1:5500',//允许的头
            ALLOW_METHODS:'PUT,POST,GET,DELETE,OPTIONS,HEAD',//允许的请求方式
            HEADERS:'Content-Type,Content-Length,Authorization,Accept ,X-Requested-With',//允许的请求头
            CREDENTIALS:true//在跨域请求中是否允许携带资源凭证Cookie
        },
        //=>SESSION存储相关信息
        SESSION:{
            secret:'ZFPX',
            saveUnintialized:false,
            resave:false,
            cookies:{
                maxAge:1000*60*60*24*30
            }
        }
    };
  • 客户端(发送 ajax/fetch 请求)
axios.defaults.baseURL = "http://127.0.0.1:8888";
axios.defaults.withCredentials = true;
axios.defaults.headers["Content-Type"] = "application/x-www-form-urlencoded";
axios,
  (defaults.transformRequest = function(data) {
    if (!data) return data;
    let result = ``;
    for (let attr in data) {
      if (!data.hasOwnProperty(attr)) break;
      result += `&${attr}=${data[attr]}`;
    }
    return result.substring(1);
  });
axios.interceptors.response.use(
  function onFulfilled(response) {
    return response.data;
  },
  function onRejected(reason) {
    return Promise.reject(reason);
  }
);
axios.defaults.validateStatus = function(status) {
  return /^(2|3)\d{2}$/.test(status);
};
  • 服务器端设置相关的头信息(需要处理 options 试探性请求)
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "http://localhost:8000"); //缺点:1.源只能写一个 2.*(就不能再允许携带Cookie了)具体地址
  res.header("Access-Control-Allow-Credentials", true);
  res.header(
    "Content-Type,Content-Length,Authorization,Accept,X-Requested-With"
  );
  res.header(
    "Access-Control-Allow-Methods",
    "PUT,POST,GET,DELETE,HEAD,OPTIONS"
  );
  if (req.method === "OPTIONS") {
    res.send("OK!");
    return;
  }
  next();
});

3. http proxy =>webpack webpack-dev-server(常用)

import axios from "axios";
axios.get("/user/list").then(res => {
  console.log(res);
});
// webpack.config.js
    devServer:{
        port:3000,//端口号
        progress:true,//显示打包进度
        contentBase:'./build',//指定静态资源访问的目录
        proxy:{
            '/':{
                target:'http://127.0.0.1:3001',
                changeOrigin:true//改变源,为true后dev-sever会做中层代理,相当于拿node写了个中间件
            }
        }
    }

4. nginx 反向代理(后端服务器处理)

www.baidu.cn->www.baidu.com

#proxy服务器
server{
    listen     80;
    sever_name www.baidu.com;
    location / {
        proxy_pass www.baidu.cn;#反向代理
        proxy_cookie_demo www.baidu.cn www.baidu.com;#处理cookie
        add_header Access-Control-Allow-Origin www.baidu.cn;
        add_header Access-Control-Allow-Credentials  true;
    }
}

5. postMessage

  • window.postMessage() 方法可以安全地实现跨源通信,它提供了一种受控机制来规避同源策略限制。
  • 语法:otherWindow.postMessage(message, targetOrigin, [transfer]);
    • otherWindow(窗口)
      • 其他窗口的一个引用,比如 iframe 的 contentWindow(嵌入的子窗口)属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
    • message(要发的消息)
      • 将要发送到其他 window 的数据。它将会被结构化克隆算法序列化.
    • targetOrigin(接收的域)
      • 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串’*’(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口
    • transfer
      • 和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
// server1.js node server1
let express = require("express"),
  app = express();
app.listen(1001, () => {
  console.log("OK!");
});
app.use(express.static("./"));
// server2.js node server2
let express = require("express"),
  app = express();
app.listen(1002, () => {
  console.log("OK!");
});
app.use(express.static("./"));
<!-- A.html -->
<iframe
  id="iframe"
  src="http://127.0.0.1:1002/MESSAGE/B.html"
  frameborder="0"
  style="display:none;"
></iframe>
<script>
  iframe.onload = function() {
    iframe.contentWindow.postMessage("hhh", "http://127.0.0.1:1002/");
  };
  // =>监听B传递的信息
  window.onmessage = function(ev) {
    console.log(ev.data);
  };
</script>
<!-- B.html -->
<script>
  // =>监听A发送过来的信息
  window.onmessage = function(ev) {
    console.log(ev.data);
    // =>ev.source:A
    ev.source.postMessage(ev.data + "@@@", "*");
  };
</script>

B 输出 hhh A 输出 hhh@@@ A 客户端->B 服务器(+@@@)->A 客户端


6. WebSocket 协议(客户端与服务器实时通信的协议)跨域

  • scoket.io 实现 客户端->服务器->客户端
<!-- 前端处理 -->
<script src="./socket.io.js"></script>
<script>
  let socket=io('http://127.0.0.1:3001/');
  //=>连接成功处理
  socket.on('connect',function(){I
      //=>监听服务端消息
      socket.on('message',function(msg){
          console.1og('data from server:'+msg);
      });
      //=>监听服务端关闭
      socket.on('disconnect',function(){
          console.log('server socket has closed!');
      });
  });
  //=>发送消息给服务器端
  socket.send("hhhh");
</script>
// 服务器端处理node express
//=>监听socket连接:server是服务器创建的服务 client客户端
socket.listen(server).on("connection", function(client) {
  //=>接收信息
  client.on("message", function(msg) {
    // =>msg客户端传递的信息
    client.send(msg + "@@");
  });
  //=>断开处理
  client.on("disconnect", function() {
    console.log("client socket has closed!");
  });
});

7. document.domain+iframe

  • 只能实现同一个主域不同子域之间的操作
  • 父页面 A http://www.baidu.cn/A.html
<iframe src="http://www.baidu.cn/B.html"></iframe>
<script>
  document.domain = "baidu.cn";
  var user = "admin";
</script>
  • 子页面 B http://www.baidu.cn/B.html
<script>
  document.domain = "baidu.cn";
  alert(window.parent.user);
</script>

8. window.name+iframe

  • 服务器需要返回给 A 的信息都在 window.name 中存储着
  • A 页面与 proxy 页面同源但与 B 不同源
    页面 A
<iframe
  src="http://127.0.0.1:1002/NAME/B.html"
  id="iframe"
  style="display:none;"
></iframe>
<script>
  let count = 0;
  iframe.onload = function() {
    if (count === 0) {
      // =>需要我们先把地址重新指向到同源中才可以
      iframe.src = "http://127.0.0.1:1001/NAME/proxy.html";
      count++;
      return;
    }
    console.log(iframe.contentWindow.name);
  };
</script>
let proxy = function(url, callback) {
  let count = 8;
  let iframe = document.createElement("iframe");
  iframe.src = url;
  iframe.onload = function() {
    if (count === 0) {
      iframe.contentWindow.location = "http://www.baidu.cn/proxy.html";
      count++;
      return;
    }
    callback(iframe.contentWindow.name);
  };
  document.body.appendChild(iframe);
};

页面 B(服务器端)

window.name='hhhhh';

9. location.hash+iframe

  • A 和 C 同源,A 和 B 非同源
  • A->B(发请求 hash 值,B 监听到)->C(监听到 B 传来的 HASH 值,操作同域的 A 的回调)->A
  • 缺点:长度限制
    页面 A
<iframe
  id="iframe"
  src="http://127.0.0.1:1002/B.html"
  style="display:none;"
></iframe>
<script>
  let iframe=document.getElementById('iframe');
  //=>向B.html传hash值
  iframe.onload=function(){
      iframe.src='http://http://127.0.0.1:1002/B.html#msg=hhhhhh';
  //=>开放给同域C.html的回调方法
  function func(res){
      alert(res);
  }
</script>

页面 B

<iframe
  id="iframe"
  src="http://127.0.0.1:1001/C.html"
  style="display:none;"
></iframe>
<script>
  let iframe = document.getElementById("iframe");
  //=>监听A传来的HASH值改变,再传给C.html
  window.onhashchange = function() {
    iframe.src = "http://127.0.0.1:1001/C.html" + location.hash;
  };
</script>

页面 C

<script>
  //=>监听B传来的HASH值
  window.onhashchange = function() {
    //=>再通过操作同域A的js回调,将结果传回
    window.parent.parent.func(location.hash);
  };
</script>

10. XMLHttpRequest

  • 发送 json 类型数据
var request = new XMLHttpRequest();
request.open("post", "/address/dosomething");
var params = {
  name: "张三",
  age: 18
};
// 发json类型
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); //用POST的时候一定要有,缺少这句,后台无法获取参数
request.send(JSON.stringify(params));

request.onreadystatechange = function() {
  if (request.readyState === 4 && request.status === 200) {
    // dealData(request.responseText)
  }
};
  • 发送表单类型数据
var request = new XMLHttpRequest();
request.open("post", "/address/dosomething");
var params = "name=zhangsan&age=18";
// 发送表单类型
request.setRequestHeader(
  "Content-Type",
  "application/x-www-form-urlencoded;charset=UTF-8"
); //用POST的时候一定要有,缺少这句,后台无法获取参数
request.send(params);

request.onreadystatechange = function() {
  if (request.readyState === 4 && request.status === 200) {
    // dealData(request.responseText)
  }
};
  • 服务端(以 express4 框架为例)
const host = req.headers.host; // 服务器host(如:"100.84.164.64:4000")
const origin = req.headers.origin; // 发送http请求的机器的顶层域名(如:"http://192.168.22.40:4000")

res.set({
  "Access-Control-Allow-Credentials": true, // 设置请求能携带cookie
  "Access-Control-Allow-Origin": origin // 仅允许http://foo.example域名下的请求跨域,允许所有域名访问
  //'Access-Control-Allow-Origin': '*', // 允许所有域名访问,不安全
  //'Access-Control-Allow-Origin': 'http://foo.example', // 仅允许http://foo.example域名下的请求跨域
});

11.node 做中间件代理

  • 安装代理模块 npm i http-proxy-middleware --save
const express = require("express");
const app = express();

/* 代理配置 start */
const proxy = require("http-proxy-middleware"); //引入代理模块
const proxyOptions = {
  target: "http:/127.0.0.1:9999", //后端服务器地址
  changeOrigin: true //处理跨域
};
const exampleProxy = proxy("/api/*", proxyOptions); //api前缀的请求都走代理
app.use(exampleProxy);
/* 代理配置 end */

const hostName = "127.0.0.1";
const port = 8080;

app.get("/", function(req, res) {
  const html = `<!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <meta http-equiv="X-UA-Compatible" content="ie=edge" />
            <title>Document</title>
        </head>
        <body>
            <button id="btn1">请求服务器接口1</button>
            <button id="btn2">请求服务器接口2</button>
            <script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
            <script>
                document.getElementById('btn1').addEventListener(
                    'click',
                    () => {
                        axios.get('/api/hello', {
                            params: {
                                key: 'hello'
                            }
                        });
                    },
                    false
                );
                document.getElementById('btn2').addEventListener(
                    'click',
                    () => {
                        axios.get('/api/word', {
                            params: {
                                key: 'word'
                            }
                        });
                    },
                    false
                );
            </script>
        </body>
    </html>`;
  res.setHeader("Content-Type", "text/html");
  res.send(html);
});
app.listen(port, hostName, function() {
  console.log(`服务器运行在http://${hostName}:${port}`);
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值