跨域原因及解决方案

跨域(CORS)原因及解决方案



什么是跨域?

浏览器的同源策略

浏览器有一个重要的安全机制,叫 同源策略(Same-Origin Policy)。它的意思是:浏览器只允许网页向和自己“同源”的地址发送请求,否则就拦截。
同源的定义是:
协议(http/ https)、域名(或 IP)、端口号 三者完全相同。
假设你的网页地址是:

http://localhost:3000/index.html

目标地址:

目标地址是否同源原因
http://localhost:3000/api/data✅同源协议、域名、端口号都一样
https://localhost:3000/api/data❌不同源协议不同(https 🆚 http)
http://127.0.0.1:3000/api/data❌不同源域名不同(localhost 🆚 127.0.0.1)

什么是跨域?

当你访问一个不同源的接口时,浏览器会因为同源策略阻止这个请求,这就是跨域。
通常控制台会出现这样的错误:
在这里插入图片描述

如何解决跨域

JSONP(比较老的方法,不推荐)

解决方法:

  1. 浏览器生成一个script元素,访问数据接口
  2. 服务器响应一段js代码,调用某个函数,并把响应数据传入

前端代码:

	function jsonp(url){
		const script = document.createElement("script");
		script.src = url;
		// 为了不影响页面,script加载过后,将其移除
		script.addEventListener("load", ()=>{
			script.remove();
		)
	}
	jsonp("请求的url地址");
	
	// 服务器返回的js函数
	function callback(data){
		console.log(data);
	}

服务器端代码:

router.get("/", async (req, res) => {
    const result = await stuServ.getStudents();
    res.status(200).send(getResult(result));
    // jsonp 实现跨域
    // 首先将 content-type的值设置为 “application/javascript”
    // 修改返回的数据,将数据放入到callback函数中
    res.header("content-type", "application/javascript").send(`callback(${JSON.stringify(result)})`);
});

JSONP的问题:

  • 会打乱服务器的消息格式:jsonp要求服务器响应js代码,但是在非跨域情况下,服务器又需要响应一个正常的json合适。
  • 只能完成GET请求:浏览器的script标签发出的请求,只能是get请求。

CORS (服务器端设置)

CORS是基于http1.1的一种跨域解决方案,它的全称是Cross-Origin Resource Sharing,跨域资源共享。
总体思路:如果浏览器要跨域访问服务器资源,需要获得服务器的允许。

三种不同的交互模式:

  • 简单请求

    • 需要同时满足的条件:
      1. 请求方法属于:get,post, head 中的一种
      2. 请求头仅包含安全的字段,常见安全字段如下:
        • Accept
        • Accept-language
        • Content-Type
        • Content-language
        • DPR
        • DownLink
        • Save-Data
        • Viewport-Width
        • Width
      3. 请求头如果包含 Content-Type,值只能为:
        • text/plain
        • multipart/form-data
        • application/x-www-form-urlencoded
    • 当浏览器判定某个ajax请求为简单请求时:
      1. 会在请求头中自动添加 Origin字段,告诉服务器是哪个源地址在跨域请求

      2. 服务器响应头中应该包含Access-Control-Allow-Origin ,允许跨域请求
        在这里插入图片描述
        代码实现:

        	const allowOrigins = ["origin1", "origin2", ...];
        	if ("origin" in req.headers && allowOrigins.includes(req.headers.origin)) {
                res.header("access-control-allow-origin", req.headers.origin);
           }
        
  • 需要预检的请求
    若浏览器判定ajax请求不是一个简单的请求,就会按照下面的流程进行:

    1. 浏览器发送预检请求(OPTIONS)询问服务器是否允许

    2. 服务器允许

    3. 浏览器发送真实请求

    4. 服务器完成真实响应
      例如:
      有一个需要预检的跨域请求:

      fetch("http://myRequest.com/api/test", {
      	method: "POST",
      	header: {
      		"Content-Type": "application/json",
      		a: "a",
      		b: "b"
      	},
      	body: JSON.stringify({
      		name: "abc",
      		age: 18
      	})
      }).then(res=>res.json()).then(data => console.log(data));
      

      此时浏览器会发送一个预检请求,询问服务器是否允许:
      在这里插入图片描述
      预检请求有以下特征:

      • 请求方法为OPTIONS
      • 没有请求体
      • 请求头中包含
        • Origin:请求的源,和简单请求的含义一致
        • Access-Control-Request-Method:后续的真实请求将使用的请求方法
        • Access-Control-Request-Headers:后续的真实请求会改动的请求头

      若服务器允许,需要在请求头中添加:

      • Access-Control-Allow-Method: 允许的后续真实请求
      • Access-Control-Allow-Headers: 允许改动的请求头
      • Access-Control-Allow-Origin:允许的源
      • Access-Control-Max-Age:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了

      示例代码:

      	const allowOrigins = ["origin1", "origin2", ...];
      		if (req.method === "OPTIONS") {
              res.header("Access-Control-Allow-Methods", req.header("access-control-request-method"));
              res.header("Access-Control-Allow-Headers", req.header("access-control-request-headers"));
          }
          if ("origin" in req.headers && allowOrigins.includes(req.headers.origin)) {
              res.header("access-control-allow-origin", req.headers.origin);
          }
      
  • 附带身份凭证的请求
    有一些场景,需要请求携带cookie,只需要在响应头中添加:Access-Control-Allow-Credentials: true即可。
    对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。
    note:
    对于跨域请求,nodejs中可以使用 cors库来实现,也可以自己封装跨域的中间件。

浏览器跨域配置(开发阶段,测试使用)

谷歌浏览器为例:
1. 新建目录(如C:\MyChromeDevUserData)。‌‌
2. 右键快捷方式→属性→在“目标”字段末尾添加 --disable-web-security --user-data-dir=C:\MyChromeDevUserData(注意参数前有空格)。‌‌
若原目标值带引号,参数需加在引号外。‌‌

webpack/vite 等工程化工具中配置

module.exports = {
  // 其他配置...
  devServer: {
    // 其他devServer配置...
    proxy: {
      '/api': {
        target: 'http://example.com', // 目标服务器地址
        changeOrigin: true, // 是否改变源地址
        pathRewrite: {'^/api' : ''}, // 重写路径
        // 其他代理配置...
      }
    }
  }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值