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

4.2 KiB
Raw Blame History

tags, create time
tags create time
跨域
localhost
同源策略
浏览器安全
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,而是有意为之的安全决策:

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)

解决方法

方法一:统一域名(最推荐)

让前后端用同一个名称:

# 方式 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 — 显式绑定 localhost
r.Run("localhost:8080")

// Node.js / Express
app.listen(8080, "127.0.0.1") // 或 "localhost"

方法二:配置 CORS(正确姿势)

如果必须混用,在后端正确设置响应头:

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 转发请求,让它变成真正的同源:

// 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)。虽然走到的是同一个办公室,但门禁系统只认牌匾上的名字——牌子不一样,就不给你刷卡。

关联笔记