---
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 预检请求机制