This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/DEV/跨域问题调试/localhost-vs-127.0.0.1.md
T

148 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<br/>即使部署在同一服务器"]
A --> C["localhost ≠ 127.0.0.1<br/>即使解析到同一个网卡"]
B --> D["最小权限原则:<br/>网络可达 ≠ 安全可信任"]
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 预检请求机制