跳到主要内容

2 篇博文 含有标签「proxy」

查看所有标签

· 阅读需 8 分钟

本文讨论浏览器的同源策略,跨域的常见解决方案及其原理

浏览器的同源策略​

同源策略只存在于浏览器环境中,是浏览器的重要安全策略之一

其中,源 = 协议 + 主机 + 端口,两个源相同,称之为同源,反之则称为跨域或跨源

同源策略是指若页面的源(地址栏)和页面运行过程中加载的源(link、script、ajax)不一致时,处于安全考虑,浏览器会对跨域的资源访问进行一些限制

主要通过以下三种方式解决跨域的问题

  • 代理
  • CORS
  • JSONP

无论哪种方式,都是在想办法让浏览器知道,我这次跨域的请求是自己人,就不要拦截了

对于具体方案的选择,参考下图

image-20240515160743964

代理​

代理适用的场景是:生产环境不发生跨域(前端服务和后端服务在一起),但开发环境发生跨域(前后端分离开发),主要解决前端开发环境的跨域问题

思路:将后端服务改成自己的开发服务器,这样一来浏览器就会允许这个 ajax 请求。然后开发服务器再将这个请求转发给后端服务器即可

比如浏览器打开 localhost:8080 页面,接口请求了 localhost:8080/api,满足同源策略,浏览器会成功(向本地开发服务器)发出这个请求。当开发服务器收到请求后,会帮我们改成正确的地址去访问

即让(开发)服务器与(后端)服务器交互,从而绕开浏览器的同源限制

打包工具都自带这个功能,配置一下即可(devServer.proxy/server.proxy)

// webpack
module.exports = {
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000', // 你想要代理到的目标 API 地址
pathRewrite: {'^/api': ''}, // 可选,重写路径
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
}
}
}
};

// vite
export default defineConfig({
server: {
port: 3000,
proxy: {
// 选项写法
'/api': {
target: 'http://localhost:8000', // 后端服务实际地址
changeOrigin: true, // 必须设置为true
rewrite: (path) => path.replace(/^\/api/, '') // 重写请求路径
},
// 简写写法
'/foo': 'http://localhost:4567/foo',
// 正则表达式写法
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
},
}
}
})

CORS​

跟前端没关系,主要是后端的处理

CORS 是基于 http 1.1 的一种跨域解决方案,它的全称是 Cross-Origin Resource Sharing 跨域资源共享

如果浏览器要跨域访问服务器的资源,需要获得服务器的允许

针对不同的请求,CORS 规定了三种不同的交互模式,分别是

  • 简单请求
  • 需要预检的请求
  • 附带身份凭证的请求

简单请求​

当请求同时满足以下条件时,浏览器会认为它是一个简单请求

  • 请求方法属于 GET、POST 或 HEAD

  • 请求头仅包含安全的字段(无自定义),例如 Accept、Content-Type、Accept-Language 等

  • 请求头如果包含 Content-Type,仅限如下值之一:text/plain、multipart/form-data 或者 application/x-www-form-urlencoded

浏览器对于简单请求,会做如下处理

  1. 给请求头自动添加 Origin 字段
  2. 服务器响应头应包含 Access-Control-Allow-Origin 字段,该字段包括如下的值
    1. * 表示全部源都可以访问
    2. 具体的且可以访问此服务器的源

需要预检的请求​

如果浏览器识别出该请求不属于简单请求,就会先发送 OPTIONS 预检请求

  1. 浏览器发送预检请求,询问服务器是否允许
OPTIONS /api/user HTTP/1.1
Host: crossdomian.com
...
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type

预检请求有如下特征:

  • 请求方法为 OPTIONS
  • 没有请求体
  • 请求头包含
    • Origin:请求的源
    • Access-Control-Request-Method:后续真实请求将使用的请求方法
    • Access-Control-Request-Headers:后续真实请求会改动的请求头
  1. 服务器允许
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Method: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400

服务器允许的情况下,只需要在响应头中添加:

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

后续发送真实请求的处理与简单请求相同

附带身份凭证的请求​

默认情况下,ajax 的跨域请求并不会附带 cookie,某些需要权限的操作就无法进行

这个问题可以通过简单的配置来解决

// xhr
xhr.withCredentials = true
// fetch
fetch(url, { credentials: 'include' })

此时,该请求就被浏览器识别为一个附带身份凭证的请求,会在预检请求头中的 Access-Control-Request-Headers 添加 cookie 或者 Authorization 字段

当服务器响应附带身份凭证的请求时,需要明确告知客户端,服务器允许这样的凭据

告知的方式是在预检响应头中添加 Access-Control-Allow-Credentials: true 即可

需要注意的是,对于这种请求,服务器不得设置 Access-Control-Allow-Origin: *

补充​

在跨域访问时,JS 只能拿到一些最基本的响应头,例如 Cache-Control,Content-Type,Expires 等

服务器可以通过设置 Access-Control-Expost-Headers: authorization, a, b 响应头使 JS 能够访问这些特殊响应头

JSONP​

当需要跨域请求时,不使用 AJAX,转而生成一个 script 元素去请求服务器,由于浏览器并不阻止 script 元素的请求,这样请求可以到达服务器

  1. 本地准备好一个 callback 函数,相当于 ajax 的回调函数

  2. 生成一个 script 标签,该 src 为服务器的地址

  3. 服务器响应一段 js 代码,将数据作为参数传入回调中,相当于执行了 callback 函数

  4. 相当于间接的把数据传递给客户端

JSONP 只支持 GET 请求

· 阅读需 1 分钟

完成这个表达式:add[1][2][3] + 4 => 10

  1. 链式读取 add 对象的属性,并且累加 key

  2. 在隐式类型转换时,要将内部的累加结果作返回

const add = new Proxy(
{ value: 0 },
{
get(target, key, receiver) {
// 对象 => 原始,会先调用对象的 Symbol.toPrimitive 方法
if (key === Symbol.toPrimitive) {
return () => Reflect.get(target, 'value')
}
target.value += Number(key)
// 支持链式读取,所以返回这个 proxy 本身
return receiver
}
}
)

console.log(add[1][2][3] + 4) // 10