js跨域问题及解决方法汇总

前言

定义

浏览器为了保证用户信息的安全,防止恶意网站窃取数据,禁止不同域之间的js交互。对于浏览器而言,只要协议、域名、端口其中有一个不同就会触发同源策略,造成跨域,从而限制交互

  • cookie、storage、indexDB等不能获取

  • ajax不能发送请求、dom树无法获得

为什么要限制跨域访问

如果一个网页可以随意的访问另一个网站的资源,就有可能在用户完全不知情的情况下出现安全问题

浏览器出于安全问题,对同源请求放行,对异源请求限制,这些限制规则统称为同源策略,因为限制造成的开发问题,称之为跨域(异源)问题

对标签发出的跨域请求轻微限制,对 AJAX 发出的跨域请求严厉限制

方法

常用方法

跨域资源共享(CORS)

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

是一套机制,用于浏览器校验请求。只要服务器明确表示允许,则校验通过;服务器明确拒绝或没有表示,则校验不通过。CORS将请求分为两类,简单请求和预检请求:

简单请求
  1. 请求方法为GET、HEAD、POST

  1. 头部字段满足CORS安全规范

  1. 请求头的Content-Type为 text/plain、multipart/form-data、application/x-www-form-urlencoded

  1. 服务器响应头,要么同源,要么放行

预检请求
浏览器发送预请求
  1. 从哪个源发送的请求:Origin:http://my.com

  1. 请求方式:Access-Controt-Request-Method:POST

  1. 请求改变了哪些请求头:Access-Controt-Request-Headers:content-type

服务器进行响应
  1. Access-Controt-Allow-Origin:http://my.com...,允许的源,可以多个

  1. Access-Controt-Allow-Method:POST,GET...,允许的方式,可以多个

  1. Access-Controt-Allow-Headers:content-type...,允许改变的请求头,可以多个

  1. Access-Controt-Max-Age:86400,多少秒不用再次检验

jsonp

前端写一个函数,服务端调用函数传入参数,通过参数获取,根本不是AJAX

// 获取返回结果
function callback(resp){
    console.log(resp);
}

// 向服务端请求js文件
function request(url){
    // 生成一个 script 标签
    const script = document.createElement('script')
    // 标签引用 js 路径为填入路径
    script.src = url
    // 标签解析完成之后删除
    script.onload = function(){
        script.remove()
    }
    // 插入 body 中
    document.body.appendChild(script)
}

Nginx代理跨域

差别
  • cors:需要服务器设置响应头

  • jsonp:需要服务器响应一段js代码,并且还要调用函数

方法
  1. 浏览器请求自己的服务器 proxy

  1. 代理服务器请求目标服务器 target

  1. 服务器之间没有跨域问题,目标服务器响应代理服务器

  1. 代理服务器返回数据

// 引入库
const express = require('express')
// 创建服务器
const app = express()
// 接受对路径 /hero 的 GET 请求
app.get('/hero',async (req,res)=>{
    // 使用 cors 解决对代理服务器的跨域
    res.header('assess-control-allow-origin','*')
    // 响应一段测试文本
    res.send('你好,我是代理服务器')
    // 在这里请求目标服务器,然后返回给前端
})
// 监听9527端口
app.listen(9527,()=>{
    console.log('服务器已启动');
})

扩展学习

Node中间件代理跨域

Vue项目一般使用此方法,就是对代理服务器的一种封装。

在vue.config.js文件中配置
module.export = {
    ...
    devServer: {
        proxy: {
            [ process.env.VUE_APP_BASE_API ]: {
                target: 'http://xxxx',    // 代理跨域目标接口
                ws: true,
                changeOrigin: true,
                pathRewrite: {
                    [ '^' + process.env.VUE_APP_BASE_API ] : ''
                }
            }
        }
    }
Node/Express中配置:
const express = require(\'express\')
const proxy = require('http-proxy-middleware')
const app = express()
app.use('/', proxy({ 
    // 代理跨域目标接口 
    target: 'http:    // xxxx:8080', 
    changeOrigin: true, 
    // 修改响应头信息,实现跨域并允许带cookie 
    onProxyRes: function(proxyRes, req, res) { 
        res.header('Access-Control-Allow-Origin', 'http://xxxx')
        res.header('Access-Control-Allow-Credentials', 'true')
    }, 
    // 修改响应信息中的cookie域名 
    cookieDomainRewrite: 'www.domain1.com'     // 可以为false,表示不修改
})); 
app.listen(3000); 

Websocket

WebSocket是HTML5标准中的一种基于TCP但是区别于HTTP的通信协议,因为是长连接,该协议不实行同源政策。以ws://(非加密)和wss://(加密)作为协议前缀。

因为WebSocket请求头信息中有Origin字段,表示请求源来自哪个域,服务器可以根据这个字段判断是否允许本次通信。

// 创建websocket
var socket = new WebSocket('ws://www.baidu.com');
// 发送消息
socket.send('hello WebSocket');
// 接收消息
socket.onmessage = function(event){
    var data = event.data;
}

postMessage

是HTML5引入的一个全新的API:跨文档通信 API(Cross-document messaging)。 这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

窗口之间传递消息
// 父窗口发起
window.opener.postMessage("我是来自a页面的","http://b.com")

// 子窗口接收
window.onmessage = function(e){
    e = e || event;
    console.log(e.data);//我是来自a页面的
}
// 子窗口发送
window.opener.postMessage('我是来自b页面的', 'http://a.com');
读取其他窗口LocalStorage
// 父窗口调用方法
var obj = { name: 'Jack' };
// 存入对象
window.parent.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://b.com');
// 读取对象
window.parent.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
window.onmessage = function(e) {
    // 判断消息是否来自a窗口
    if (e.origin != 'http://a.com') return;
    // "Jack"
    console.log(JSON.parse(e.data).name);
}

b窗口接收消息,获取自己的localStorage之后再传递给a窗口

window.onmessage = function(e) {
  // 消息来自自己,不予理会
  if (e.origin !== 'http://b.com') return;
  var payload = JSON.parse(e.data);
  // 判断方法进行操作
  switch (payload.method) {
    case 'set':
      localStorage.setItem(payload.key, JSON.stringify(payload.data));
      break;
    case 'get':
      var parent = window.parent;
      var data = localStorage.getItem(payload.key);
      // 如果是获取,获取之后传递给a窗口
      parent.postMessage(data, 'http://a.com');
      break;
    case 'remove':
      localStorage.removeItem(payload.key);
      break;
  }
}

单向跨域

利用浏览器对标签发出的跨域请求轻微限制,进行单方面的跨域请求

  • <img src='' />标签发送get请求

  • <script src=""></script>标签嵌入跨域脚本,语法错误信息只能在同源脚本中捕捉到。

  • <link src="">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。

  • <video>、<audio>嵌入多媒体资源。

  • <object>、<embed>、<applet>的插件。

  • @font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。

  • <iframe> 和 <iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

猜你喜欢

转载自blog.csdn.net/wind_notcare/article/details/129548232