📖 文章导航
- 关于跨域问题
- 同源策略
- 跨域资源共享
- 解决方案
- 前端代理
- 后端
- 服务端代理
关于跨域问题
同源策略
同源策略(Same-origin policy
)是浏览器中一个重要的安全策略,它用于限制不同源之间的资源交互。其目的是为了帮助阻隔恶意文档,减少可能被攻击的媒介。
同源的定义
同源三要素:协议 + 域名 + 端口
示例:https://www.baidu.com/index.html
网址 | 是否同源 | 描述 |
---|---|---|
https://www.baidu.com/about.html | ✅ | 只有路径不同 |
http://www.baidu.com/index.html | ❌ | 协议不同 http |
https://www.baibu.com/index.html | ❌ | 很明显域名 (主机) 不同 |
https://www.baidu.com:8000/index.html | ❌ | 端口号不同 http 默认是 80 端口 |
由于同源策略的存在,所以不同源之间资源加载存在跨域问题。
跨域问题只存在于浏览器中,APP、小程序中不存在
– HTML 标签中许多 src 属性是可以跨域请求的,比如 img script 等
跨域资源共享
跨域资源共享 (CORS
, Cross Origin Resource Sharing
) 是一种基于 HTTP 头的机制,该机制可以通过服务器指定能够访问它的源,这使浏览器允许这些源访问加载自己的资源。
当浏览器认为即将要执行的请求可能会对服务器造成不可预知的影响时,会在正式请求资源之前,先发送一个preflight 预检请求 (preflight 请求类型为 OPTIONS),根据服务器响应判断是否允许即将执行的请求。
响应头中通常包含这些信息:
Access-Control-Allow-Headers: Content-Type, Content-Length, Authorization, Accept, X-Requested-With
Access-Control-Allow-Origin: http://www.baidu.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
其中 Access-Control-Allow-Origin
就表示当前服务器允许的源,如果与当前源不匹配或不存在该字段,就会触发跨域错误。
解决方案
前面讲到跨域问题只存在于浏览器中,有几点需要理解:
- 在默认情况下,浏览器发送的跨域请求,实际上服务器是响应了的,只不过响应结果被浏览器拦截了,因为同源策略。细分的话有两种情况:
1.简单请求:服务器处理并相应;
2.复杂请求:浏览器会发送预检请求,服务器只是应答了预检请求,预检请求的响应结果被浏览器拦截,所以并未收到真正的业务请求,也就不会处理真实业务逻辑。
前端代理
以前端工程化为基础,忽略了 JSONP 等跨域解决方案
应用场景: 本地开发
前端代理是如何解决跨域问题的?
通过 node
中间件实现代理来转发我们的请求,由于 node
本身不受浏览器的限制,所以可以和服务器正常通讯。简单示意图如下:
webpack-dev-server
前端基于 webpack 进行打包的项目都会以 webpack-dev-server 来运行,前端可以在开发环境设置代理解决跨域问题:
devServer: {
proxy: {
'/api': {
target: 'http://target.origin',
changeOrigin: true, //是否跨域
pathRewrite: { '^/api': '' }
}
}
}
vite
vite 与 webpack 的写法略有不同,更多写法可参考 Vite 官方中文文档
server: {
proxy: {
'/api': {
target: 'http://target.origin',
changeOrigin: true, //是否跨域
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
后端
应用场景: 应用比较少吧,因为开发环境和生产环境部署时的源不相同,而且每次在不同的服务器部署时都需要改代码去调整允许访问的源也不现实。一般也不建议设为 *。
后端是如何解决跨域问题的?
通过配置允许的源,在响应头中将请求源添加到 Access-Control-Allow-Origin 中,浏览器同源策略就能判定为同源请求。不同框架的 API 可能不同,但方法都是一样的,下面是 express 的示例:
// 判断当前源是否在允许列表内
const isAllowedOrigin = (origin) => {
// 同时设定多个允许的源
const allowedOrigins = ['http://localhost:3300', 'http://127.0.0.1:3300'];
return allowedOrigins.includes(origin);
};
app.all('*', function (req, res, next) {
const { origin } = req.headers;
if (!isAllowedOrigin(origin)) {
return;
}
res.header('Access-Control-Allow-Origin', origin);
next();
});
服务端代理
应用场景: 线上部署
Nginx 反向代理
前端分离项目部署通常也都需要解决跨域问题,因为前后端项目部署在不同的端口,甚至不同的服务器上。通过 Nginx 转发请求,不会触发同源策略。
{
server {
listen 3000;
server_name localhost;
# 前端部署配置
location / {
root /var/www/app; # 配置应用的文件夹
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# 代理后端 API 的配置
location /api/ { # 用于转发的后端地址标志
proxy_pass http://192.168.0.1:8080/;# 后端部署的真实地址
}
}