跨域方案详解

239 阅读4分钟

首先我们来了解什么是跨域

浏览器的同源策略(http协议、域名、协议三者相同)只要有一项不同,就是跨域请求,同源策略(Sameoriginpolicy)是一种约定。

协议的默认端口号

 http的默认端口号是80,例如:http://www.baidu.com和http:80//www.baidu.com是同源的。
 
 https的默认端口号是443,例如:https://www.baidu.com和http:443//www.baidu.com是同源的。

跨域,是指当前网站不能执行其他网站的脚本,他是由于浏览器的同源策略造成的,是浏览器对Javascript的安全限制。当然,如果自己做一个完全同源的网站,那么就不用考虑跨域的问题。但是,在日常开发项目中,往往跨域的请求比较多,

那么为什么会导致跨域呢?

  • 服务器分离:WEB服务器、数据服务器、图片服务器…
  • 云信息共享:第三方API接口
  • 有助于分离开发:开发跨域、部署同源

跨域的方法

proxy跨域

proxy是我们比较常用的一种跨域方式,它是利用在本地启动一个服务器,一方面实现页面的预览,一方面实现代理发送数据请求。此时我们在发送数据请求,就要先发送给本地启动的服务,再由本地服务器发送给指定的服务器,因为服务器和服务器之间没有同源策略的限制。

本地开发的时候我们一般使用,webpack_dev_server或者nodejs来启动本地服务,而部署到服务器的时候,一般基于nainx反向代理来处理。

node.js实现

1.的依赖文件-package-json中的依赖

{
  "name": "crossdomain",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "dependencies": {
    "axios": "^0.21.1",
    "blueimp-md5": "^2.18.0",
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "qs": "^6.9.4",
    "request": "^2.88.2"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

2.件夹中创建一个server-proxy.js文件

/*-CREATE SERVER-*/
const express = require('express'),
    request = require('request'),
    app = express();
app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`));

// 服务器接收到客户端发送过来的请求
app.get('/aaa', (req, res) => {
    // 向简书发送相同请求,从简书服务器获取想要的数据「不存在域的限制的」
    let jianURL = `https://www.jianshu.com/asimov/subscriptions/recommended_collections`;
    req.pipe(request(jianURL)).pipe(res);
});

/* STATIC WEB */
app.use(express.static('./'));

3.跨域的时候在当前文件夹中打开dos命令窗口用 node server-proxy.js启动即可

4.需要发送请求的时候直接发送给代理服务器即可

   axios.get('/aaa').then(response => {
        console.log(response.data);
    })

webpack-dev-server实现

1. 全局安装 @vue/cli

使用npm安装脚手架,$ npm i @vue/cli -g

2. 基于脚手架创建项目

$ vue create demo

3. 进入到这个目录

$ cd demo

4. 安装axios和配置config

$ npm i axios qs

5.在vue.config.js中配置webpack-dev-server

const ENV = process.env.NODE_ENV;
module.exports = {
    lintOnSave: ENV !== 'production',
    publicPath: './',
    productionSourceMap: false,
    // 对webpack-dev-server的配置
    devServer: {
        // 配置跨域代理「可以配置对多台服务器的代理」
        proxy: {
            // 所有以“/api”开始的请求,都发送到代理服务器「走这个配置」
            "/api": {
                target: "https://www.jianshu.com/asimov",
                ws: true, //开启webscoket协议
                changeOrigin: true, //改变origin源
                pathRewrite: {
                    //将以/api替换为空  /api只是为了区分代理到那台服务器
                    "^/api": ""
                }
            },
            // 所有以“/zhihu”开始的请求,都代理到知乎服务器
            "/zhihu": {
                target: "https://news-at.zhihu.com/api/4",
                ws: true,
                changeOrigin: true,
                pathRewrite: {
                    "^/zhihu": ""
                }
            }
        }
    }
};

6.在main.js中基于axios发送请求

import { createApp } from 'vue';
import App from './App.vue';

import axios from 'axios';

// 真实发送请求的地址:http://127.0.0.1:3000/api/subscriptions/recommended_collections
//   + 不加pathRewrite:向简书这样发送请求  https://www.jianshu.com/asimov/api/subscriptions/recommended_collections
//   + 设置了pathRewrite:https://www.jianshu.com/asimov/subscriptions/recommended_collections
axios.get('/api/subscriptions/recommended_collections')
    .then(response => {
        console.log('简书:', response.data);
    });

axios.get('/zhihu/news/latest')
    .then(response => {
        console.log('知乎:', response.data);
    });

const app = createApp(App);
app.mount('#app');

7.$ npm run serve 启动本地服务

CORS跨域资源共享

CORS跨域资源共享 默认因为浏览器的安全策略 是不允许ajax跨域访问的 但是如果服务器设置了 Axxess-Control-Allow-Origin 响应头信息 设置允许这个源发送请求 那么浏览器也就不会去限制了,一般都是由后端设置。

1.由服务器设置允许源

Access-Control-Allow-Orgin 设置允许的源[白名单机制]
Access-Control-Allow-Credentials 是否允许携带资源凭证  例如cookie

服务端设置了允许源   则客户端也要配合设置允许

axios.defaults.withCredentials = true; axios设置跨域是否允许携带资源凭证
xhr.withCredentials = true 原生ajax设置跨域是否允许携带资源凭证
Access-Control-Allow-Headers 设置允许的请求头
Access-Control-Allow-Methods 设置允许的请求方式

2.在CORS跨域共享中 客户端先会发送一个OPTIONS试探请求 验证是否成功 连接后成功后 再发送真正的请求

本地使用node实验

/*-CREATE SERVER-*/
const express = require('express'),
	app = express();
app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`));

/*-MIDDLE WARE-*/
// 设置白名单
let safeList = ["http://127.0.0.1:5500", "http://127.0.0.1:3000", "http://127.0.0.1:8080"];
app.use((req, res, next) => {
	let origin = req.headers.origin || req.headers.referer || "";
	origin = origin.replace(/\/$/g, '');
	origin = !safeList.includes(origin) ? '' : origin;
	res.header("Access-Control-Allow-Origin", origin);
	res.header("Access-Control-Allow-Credentials", true);
	res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
	res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
	req.method === 'OPTIONS' ? res.send('OK') : next();
});

/*-API-*/
app.get('/list', (_, res) => {
	res.send({
		code: 0,
		message: 'zhufeng'
	});
});

/* STATIC WEB */
app.use(express.static('./'));

同样使用node server-cors.js 启动本地服务器

JSONP跨域

JSONP是利用了script的src不存在跨域的限制来实现的,同样< img > 、 < link > 、 < iframe >等标签也不存在跨域的限制

1.基于 < script > 的src指定请求的地址, 实现请求的发送「 没有域的限制」; 我们会创建一个“ 全局函数”, 在发送请求的时候, 会基于问号传参, 把全局函数“ 名” 基于“ callback「 自己定义: 和服务器商量好的」” 传递给服务器

 const fn = function fn(data) {
   console.log(data)
  };
  <script src = 'http://127.0.0.1:1001/list?callback=fn' >

2.服务器会收到对应的请求, 并且基于“callback” 获取传递过来的函数名 + 准备数据

 服务器会收到对应的请求, 并且基于“ callback” 获取传递过来的函数名 + 准备数据[{
      id: 1,
      name: 'xxx'
    }, {
      id: 2,
      name: 'xxx'
    }, ...] +
    把函数名和数据拼接在一起, 成为指定格式的字符串“ fn([{
      id: 1,
      name: 'xxx'
    }, {
      id: 2,
      name: 'xxx'
    }, ...])” +
    最后返回给客户端这个字符串

3.客户端获取到“ fn(...)” 字符串, 因为基于 < script > 发送的,所以会默认把其当做js代码执行 + 也就是把全局函数fn执行,并且把服务器准备的数据当做实参传递给了函数 + 这也是为啥fn必须是全局函数了,如果是私有的, 在此处执行的时候,找不到私有的fn 问题; 注意:JSONP只能发送get请求,因为script本身只有get请求,无法发送post请求,且jsonp一定需要服务器端的配合