第十六节:跨域的原因及常用跨域方案的总结(同源部署、CORS、node代理、Nginx反向代理等)

一.  跨域简介

 PS:之前.Net系列中的跨域总结

   https://www.cnblogs.com/yaopengfei/p/10340434.html  (包含jsonp、script等古老的方式)

   https://www.cnblogs.com/yaopengfei/p/11191938.html

 

1. 为什么会产生跨域?

  浏览器存在一个“同源策略”,是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介(即两个不同源,从浏览器发出请求,是不能直接交互的)

  如果两个URL的 协议、主机、端口,都相同,则就是同源的,就不存在的跨域的问题.

  详见图1:

2. 什么是跨域?

  两个URL的如果是不同源的,从浏览器中发出请求,则就会出现跨域问题,即无法请求成功。

  如下图2:

Server代码:

const Koa = require("koa");
const app = new Koa();
app.listen(9090, () => {
	console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();

// Get请求
router.get("/getUserInfo", (ctx, next) => {
	ctx.body = {
		status: "ok",
		msg: "请求成功",
		data: [
			{ id: 001, name: "ypf1", age: 18 },
			{ id: 002, name: "ypf2", age: 18 },
			{ id: 003, name: "ypf3", age: 25 },
		],
	};
});

客户端代码: 

<body>
    <h1>测试跨域——同源部署</h1>
    <script>
        // 发送-网络请求
        fetch('http://127.0.0.1:9090/getUserInfo').then(async res => {
            const result = await res.json()
            console.log(result)
        })
    </script>
</body>

3. 实操准备

  【npm install koa koa-static @koa/router】 -- koa相关

  【npm install express】 -- express相关

  【npm install http-proxy-middleware】 -- 代理中间件   (webpack中实际上使用就是这个中间件)

 

二. 同源部署

1. 说明  

  几年前,当还没有前后端分离的时候,前端页面和接口是融合在一起的,即 都部署在同一个地址/端口下,也就不存在跨域问题。  

  所以这里所谓的同源部署,就是将 前端页面 和 Api接口部署在 同协议、同主机、端口下

2. 实操

  Server端开启:app.use(static("../client"));

  在浏览器中输入:http://127.0.0.1:9090/01-跨域_同源部署演示.html

  结果:可以请求成功

server代码:

app.listen(9090, () => {
	console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();

// 将前端页面的也部署在这个服务器下
app.use(static("../client"));

// Get请求
router.get("/getUserInfo", (ctx, next) => {
	ctx.body = {
		status: "ok",
		msg: "请求成功",
		data: [
			{ id: 001, name: "ypf1", age: 18 },
			{ id: 002, name: "ypf2", age: 18 },
			{ id: 003, name: "ypf3", age: 25 },
		],
	};
});

客户端代码:

<body>
    <h1>测试跨域——同源部署</h1>
    <script>
        // 发送-网络请求
        fetch('http://127.0.0.1:9090/getUserInfo').then(async res => {
            const result = await res.json()
            console.log(result)
        })
    </script>
</body>

 

三.  CORS跨域资源共享

1. 含义

跨源资源共享(CORS,Cross-Origin Resource Sharing跨域资源共享):

(1).它是一种基于http header的机制

(2).该机制通过允许服务器标示除了它自己以外的其它源(域、协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。

 

2. CORS分类

浏览器将 CORS 请求分成两类:简单请求和非简单请求。

◼ 只要同时满足以下两大条件,就属于简单请求(不满足就属于非简单请求)(了解即可)。

◼ 请求方法是以下是三种方法之一: HEAD GET POST

◼ HTTP 的头信息不超出以下几种字段:

   Accept

   Accept-Language

   Content-Language

   Last-Event-ID

   Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

 

3. 实操

 虽然简单请求和非简单请求配置有差距,但后者是包含前者的,所以按照非简单请求配置即可

 Server端口:通过一个中间件配置一下header参数,核心参数为:Access-Control-Allow-Origin,详见:02-Server(cors).js

 在浏览器中访问:open with live server的方式访问 5050端口,02-cors方案.html页面

 结果:可以请求成功。

server代码:

app.listen(9090, () => {
	console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();

// CORS---针对简单请求
/* app.use(async (ctx, next) => {
	// 1.允许开启CORS
	// * 代表所有源, 也可以写某个具体源:比如:http://127.0.0.1:8090
	ctx.set("Access-Control-Allow-Origin", "*");
	await next();
}); */

// CORS---针对非简单请求
app.use(async (ctx, next) => {
	//1.允许开启CORS--简单请求
	ctx.set("Access-Control-Allow-Origin", "*");
	//2.复杂请求还需要额外开启后面的代码
	ctx.set(
		"Access-Control-Allow-Headers",
		"Accept, AcceptEncoding, Connection, Host, Origin"
	);
	ctx.set("Access-Control-Allow-Credentials", true); // cookie
	ctx.set(
		"Access-Control-Allow-Methods",
		"PUT, POST, GET, DELETE, PATCH, OPTIONS"
	);
	// 3.发起的是一个options请求
	if (ctx.method === "OPTIONS") {
		ctx.status = 204; //表示 no content
	} else {
		await next();
	}
});

// Get请求
router.get("/getUserInfo", (ctx, next) => {
	ctx.body = {
		status: "ok",
		msg: "请求成功",
		data: [
			{ id: 001, name: "ypf1", age: 18 },
			{ id: 002, name: "ypf2", age: 18 },
			{ id: 003, name: "ypf3", age: 25 },
		],
	};
});

客户端代码:

<body>
    <h1>测试Cors</h1>
    <script>
        // 发送-网络请求
        fetch('http://127.0.0.1:9090/getUserInfo').then(async res => {
            const result = await res.json()
            console.log(result)
        })
    </script>
</body>

 

四. node代理服务器 【开发环境推荐】 

1. 含义

 首先,不能跨域是因为浏览器的同源策略导致的如果是node服务器,就可以任意访问,随便跨域了,所以 在浏览器 和 后台api中间,加一个中转,这个中转就是node服务器, 但是node服务器和浏览器还是存在跨域问题,这里 对node服务器自身而言,还是要用 那两套方案:

    1 部署同源部署

    2 开启cors

 这里通常采用同源部署。

2. 实操

 (1). 安装代理中间件 【npm install http-proxy-middleware】

 (2). 配置代理服务器,这里为了区分,使用express进行配置

    A. 需要和前端页面配置同源策略,解决前端和代理服务器直接的跨域问题。 app.use(express.static("../client"));

    B. 配置代理:

       监听请求中含 /api/的,然后进行重写为空字符串"", 然后使用target中的地址进行拼接,target中的地址就是api服务器的地址

 (3). 前端页面,请求的地址要改为 代理服务器的地址,且以api开头

      如:http://127.0.0.1:8080/api/getUserInfo

 (4).启动测试:

    代理服务器: 8080    【proxy-server.js】

    api服务器:  9090    【 03-Server(纯ApiServer).js 】

    前端: http://127.0.0.1:8080/03-node代理方案.html   【这里必须用代理服务器的地址启动,否则前端和代理之间也存在跨域问题哦】

   测试结果:最终前端页面的里的地址 http://127.0.0.1:8080/api/getUserInfo  会转换成: http://127.0.0.1:9090/getUserInfo,访问成功

api-server代码:

app.listen(9090, () => {
	console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();

// Get请求
router.get("/getUserInfo", (ctx, next) => {
	ctx.body = {
		status: "ok",
		msg: "请求成功",
		data: [
			{ id: 001, name: "ypf1", age: 18 },
			{ id: 002, name: "ypf2", age: 18 },
			{ id: 003, name: "ypf3", age: 25 },
		],
	};
});

代理服务器代码: 

const express = require("express");
const app = express();
const { createProxyMiddleware } = require("http-proxy-middleware");

// 代理服务器和前端页面进行同源部署
app.use(express.static("../client"));

// 代理配置
app.use(
	"/api",
	createProxyMiddleware({
		target: "http://localhost:9090",
		pathRewrite: {
			"^/api": "", //表示请求地址中 xxxx/api 会被重写为空"", 然后转换成上述的target中的  http://localhost:9090 + 后面的地址
		},
	})
);

app.listen(8080, () => {
	console.log("------------express服务器启动成功了, 端口为 8080------------");
});

客户端代码: 

<body>
    <h1>Node代理方案</h1>
    <script>
        // 发送-网络请求  (这里请求的是代理服务器)
        fetch('http://127.0.0.1:8080/api/getUserInfo').then(async res => {
            const result = await res.json()
            console.log(result)
        })
    </script>
</body>

 

3. 扩展webpack代理配置

   详见官网:https://webpack.docschina.org/configuration/dev-server/#devserverproxy

   webpack也是基于 中间件 http-proxy-middleware 来封装的

   代码如下:

 

 

4. 扩展vite的代理配置

  详见官网:https://cn.vitejs.dev/config/server-options.html#server-proxy

  代码如下:

 

五. nginx反向代理 【生产环境推荐】 

1. 含义

  这里的nginx相当于一个中转的代理服务器(nginx和api-server之间不存在跨域哦),用于转发 前端的请求 给 api-server,但是 前端和nginx之间, 同样存在跨域问题,这里需要给nginx配置cors,最终就实现 前端→ proxy-server → api-server

2. 实操

 (1). nginx配置

     监听7070端口,反向代理到 http://127.0.0.1:9090 , 同时配置cors,便于前端可以访问nginx

 (2). 启动

    Nginx服务器:7070

    api服务器:  9090    【 03-Server(纯ApiServer).js 】

    前端: http://127.0.0.1:5050/11-跨域方案总结/client/04-nginx反向代理方案.html   【open with live server的方式   5050端口

测试结果:  最终前端页面的里的地址 http://127.0.0.1:7070/api/getUserInfo  会转换成: http://127.0.0.1:9090/getUserInfo  

api-server
app.listen(9090, () => {
	console.log("---------------koa服务器启动成功,端口9090----------------");
});
//路由
const koaRouter = require("@koa/router");
const router = new koaRouter();

// Get请求
router.get("/getUserInfo", (ctx, next) => {
	ctx.body = {
		status: "ok",
		msg: "请求成功",
		data: [
			{ id: 001, name: "ypf1", age: 18 },
			{ id: 002, name: "ypf2", age: 18 },
			{ id: 003, name: "ypf3", age: 25 },
		],
	};
});
nginx服务器:
server {
        listen       7070;          # 监听的端口
        server_name  localhost;		# 监听的地址
        # 通用匹配
        location / {
			#配置cors
			add_header 'Access-Control-Allow-Origin' *;	
			add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS';
			add_header 'Access-Control-Allow-Credentials' 'true';
			add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent';
			add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            # option类请求是跨域的先验请求
			if ($request_method = 'OPTIONS') {
				return 204;   # 204代表正常
			}
			#代理地址
            proxy_pass   http://127.0.0.1:9090;
        }
		
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }
客户端代码:
<body>
    <h1>Nginx反向代理方案</h1>
    <script>
        // 发送-网络请求  (这里请求的是代理服务器)
        fetch('http://127.0.0.1:7070/getUserInfo').then(async res => {
            const result = await res.json()
            console.log(result)
        })
    </script>
</body>

 

 

六. 其它方案 

  jsonp

  script标签

  websocet、SignalR

  postMessage

  chrome的配置

 

详见开篇之前的文档

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2023-03-03 13:32  Yaopengfei  阅读(540)  评论(2编辑  收藏  举报