--- tags: - 跨域 - localhost - 同源策略 - 浏览器安全 create time: 2026-04-29 15:30 --- # localhost vs 127.0.0.1 —— 开发环境最常见的"伪同源"坑 ## 概述 记录一个高频踩坑场景:前端访问 `localhost`,后端监听 `127.0.0.1`,浏览器却报了 CORS 错误。它们明明指向同一台机器,为什么不算同源? ## 正文 ### 现象 ``` 页面: http://localhost:3000 请求: http://127.0.0.1:8080/api/user 结果: ❌ CORS blocked — "No 'Access-Control-Allow-Origin' header" ``` 开发者百思不得其解:`localhost` 不就等于 `127.0.0.1` 吗? ### 真相:浏览器不认"等价",只认"相同" 浏览器的**同源策略(Same-Origin Policy)**判定标准是严格的字符串比较: ``` 同源 = 协议完全相同 && 域名完全相同 && 端口完全相同 ``` 这里的"域名"是一个**身份标识字符串**,不做任何语义换算。`localhost` 和 `127.0.0.1` 是两个不同的字符串,所以浏览器认为这是**两个不同的源**。 > **思考**:如果浏览器把 `localhost:3000` 和 `127.0.0.1:3000` 视为同源,会有什么问题? ### 为什么浏览器要这样设计? 这不是 bug,而是有意为之的安全决策: ```mermaid flowchart LR A["域名 = 身份标识"] --> B["a.example.com ≠ b.example.com
即使部署在同一服务器"] A --> C["localhost ≠ 127.0.0.1
即使解析到同一个网卡"] B --> D["最小权限原则:
网络可达 ≠ 安全可信任"] C --> D ``` | 原因 | 说明 | |------|------| | **子域名隔离** | 同机运行多个子域名站点,不应互相读取对方数据 | | **Host Header 攻击面** | 如果有人伪造 Host 头写成 `localhost`,浏览器不能假设"IP 一样就是安全的" | | **一致性保证** | 如果浏览器开始做"智能换算"(IP↔域名等价),规则会变得极其复杂且容易出漏洞 | ### 常见触发路径 ``` 前端 dev server (Vite/Webpack) → 默认 host = localhost:3000 ↓ Node.js / Go / Python 后端 → 默认 bind = 127.0.0.1:8080(更安全) ↓ 浏览器报 CORS 错误 ``` | 框架 | 默认绑定地址 | |------|------------| | Vite dev server | `localhost` | | Webpack devServer | `localhost` | | Node.js `listen()` | `0.0.0.0`(全接口,但也可能绑定 `127.0.0.1`) | | Python Flask debug | `127.0.0.1` | | Gin (`r.Run(":8080")`) | `0.0.0.0`(但显式写 `r.Run("127.0.0.1:8080")` 时就是 `127.0.0.1`) | ### 解决方法 #### 方法一:统一域名(最推荐) 让前后端用同一个名称: ```bash # 方式 A:全部用 localhost 前端: http://localhost:3000 后端: http://localhost:8080 # 方式 B:全部用 127.0.0.1 前端: http://127.0.0.1:3000 后端: http://127.0.0.1:8080 ``` 修改后端监听地址: ```go // Go — 显式绑定 localhost r.Run("localhost:8080") // Node.js / Express app.listen(8080, "127.0.0.1") // 或 "localhost" ``` #### 方法二:配置 CORS(正确姿势) 如果必须混用,在后端正确设置响应头: ```go origin := c.Request.Header.Get("Origin") if origin != "" { c.Header("Access-Control-Allow-Origin", origin) } c.Header("Access-Control-Allow-Credentials", "true") ``` 详见 `[[GIN/3-middleware/cors-registration-scope]]`。 #### 方法三:开发代理(最省事) 用 Vite proxy 转发请求,让它变成真正的同源: ```js // vite.config.js export default { server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, } } } } ``` 请求流程变为: ``` 浏览器 → localhost:3000/api/user (同源) ↓ Vite devServer 转发 localhost:8080/api/user ``` > **生活化类比**:你家公司大门写着"大厦A座3层"(localhost),另一扇消防通道门贴着"3层回廊"(127.0.0.1)。虽然走到的是同一个办公室,但门禁系统只认牌匾上的名字——牌子不一样,就不给你刷卡。 ## 关联笔记 - [[跨域问题调试]] — 完整的跨域排查指南 - [[GIN/3-middleware/cors-registration-scope]] — CORS 中间件注册方案 - [[GIN/3-middleware/cors-preflight]] — OPTIONS 预检请求机制