红月公益电竞活动中心

GET/POST 的区别:从“为什么登录请求不能用 GET”说起

2026-07-21 22:29:44 主播频道 9412

上周我在审查一个新人提交的登录接口代码时,发现他用了 GET 请求:

js

复制代码

// ❌ 危险!不要这样写登录接口

fetch(`/api/login?username=${username}&password=${password}`, {

method: 'GET'

})

我问他:"如果用户在公共电脑上登录,别人能通过浏览器历史记录看到他的密码吗?"

他愣住了------这正是 GET 和 POST 最核心的区别。今天我就结合真实业务场景,带你彻底搞懂 HTTP 方法的本质差异。

一、问题场景:登录接口的安全隐患

我们有个后台管理系统,登录模块最初是这样设计的:

js

复制代码

// 前端代码

function login(username, password) {

return fetch(`/login?user=${encodeURIComponent(username)}&pass=${encodeURIComponent(password)}`, {

method: 'GET'

})

}

结果运维报警:Nginx 日志里全是明文密码!

因为 GET 请求的参数会出现在:

URL 中

浏览器地址栏

服务器访问日志

代理服务器日志

Referer 头

任何能看到这些地方的人,都能看到用户的密码。

二、解决方案:改用 POST 提交敏感数据

我们重构为 POST 请求:

js

复制代码

// ✅ 安全做法

function login(username, password) {

return fetch('/api/login', {

method: 'POST',

headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify({ username, password })

})

}

现在密码不会出现在 URL 和日志中,安全性大幅提升。

三、原理剖析:从表面到网络层的五层差异

1. 第一层:数据传输位置(最核心区别)

方法

数据位置

是否可见

GET

URL 查询参数(?key=value)

✅ 明文可见

POST

请求体(Request Body)

❌ 不直接可见

我们来画一张 HTTP 请求结构对比图:

ET /api/data?name=Alice&age=25 HTTP/1.1

Host: example.com

User-Agent: Chrome

→ 参数在第一行,随请求头一起发送

POST /api/login HTTP/1.1

Host: example.com

Content-Type: application/json

Content-Length: 38

{"username":"Alice","password":"123"}

↑ 参数在空行之后,作为请求体独立存在

2. 第二层:缓存与历史记录

方法

能否被缓存

是否保存在浏览器历史

GET

✅ 可以(除非禁用)

✅ 是

POST

❌ 通常不缓存

✅ 但参数不可见

js

复制代码

// GET 可以被浏览器缓存,提升性能

fetch('/api/config', { method: 'GET' }) // 可能读取缓存

// POST 每次都会发请求

fetch('/api/submit', { method: 'POST' }) // 总是发送新请求

3. 第三层:数据大小限制

方法

数据大小限制

原因

GET

约 2KB - 8KB

受 URL 长度限制(浏览器和服务器限制)

POST

几 MB 甚至更大

请求体无理论限制(受服务器配置影响)

js

复制代码

// ❌ GET 无法发送大文件

fetch(`/upload?file=${hugeBase64Data}`) // 可能失败

// ✅ POST 可以发送大文件

fetch('/upload', {

method: 'POST',

body: fileData // 可达几十 MB

})

4. 第四层:幂等性与安全性

方法

幂等性

安全性

GET

✅ 幂等

✅ 安全(只读)

POST

❌ 非幂等

❌ 不安全(可能修改数据)

PUT

✅ 幂等

❌ 不安全

DELETE

✅ 幂等

❌ 不安全

PATCH

❌ 非幂等

❌ 不安全

📌 术语解释:

幂等 :多次执行结果相同(如 GET /user/1 调 10 次结果一样)

安全:不会修改服务器状态

5. 第五层:典型使用场景

方法

使用场景

示例

GET

获取数据、搜索、查询

/api/users, /search?q=keyword

POST

创建资源、提交表单、登录

/api/users, /login

PUT

完全替换资源

PUT /api/users/1(更新整个用户对象)

DELETE

删除资源

DELETE /api/users/1

PATCH

部分更新资源

PATCH /api/users/1(只改邮箱)

四、对比主流 HTTP 方法

方法

数据位置

幂等

安全

典型用途

GET

URL 参数

查询、获取

POST

请求体

创建、提交

PUT

请求体

替换

DELETE

通常无

删除

PATCH

请求体

部分更新

HEAD

获取元信息(如检查资源是否存在)

OPTIONS

预检请求(CORS)

五、实战避坑指南

❌ 错误用法:用 GET 提交敏感信息

js

复制代码

// 千万不要这样做!

fetch(`/transfer?to=Bob&amount=1000&token=${token}`, { method: 'GET' })

✅ 正确做法:敏感操作用 POST/PUT

js

复制代码

fetch('/api/transfer', {

method: 'POST',

body: JSON.stringify({ to: 'Bob', amount: 1000 }),

headers: { 'Authorization': `Bearer ${token}` }

})

❌ 错误用法:用 POST 实现查询接口

js

复制代码

// 虽然能用,但违反 REST 原则

fetch('/search', {

method: 'POST',

body: JSON.stringify({ q: 'react' })

})

✅ 正确做法:查询用 GET,即使参数复杂

js

复制代码

// 方案1:用 GET + 查询参数

fetch(`/search?q=react&tags=hooks,context&sort=desc`)

// 方案2:如果参数太复杂,考虑用 POST 但命名要明确

fetch('/api/advanced-search', {

method: 'POST',

body: complexQuery

})

六、举一反三:三个变体场景实现思路

需要上传文件并携带元数据

使用 POST 或 PUT,Content-Type: multipart/form-data,文件和字段一起发送。

实现"撤销删除"功能

删除用 DELETE,但服务器端改为标记删除(is_deleted=true),前端用 GET /items?deleted=true 查看已删项。

批量操作接口设计

批量创建:POST /api/users(数组 body)

批量删除:POST /api/users/delete(ID 数组)或 DELETE /api/users(带 body)

批量更新:PATCH /api/users/batch(操作列表)

js

复制代码

// 批量删除示例

fetch('/api/users/batch-delete', {

method: 'POST',

body: JSON.stringify({ ids: [1, 2, 3] })

})

小结

GET 和 POST 的区别,本质是 "安全查询" vs "数据操作" 的哲学差异:

GET 是"读卡器"------只能读,不能写,数据暴露在外

POST 是"投递箱"------内容密封,专门用于提交

查数据,用 GET;

改数据,用 POST;

传敏感,绝不用 GET;

想幂等,选 PUT/DELETE。

当你在设计 API 时,先问自己:

这个操作会修改数据吗?→ 不能用 GET

数据是否敏感?→ 不能用 GET

是否可能重复提交?→ 考虑幂等性(PUT vs POST)

是创建还是更新?→ POST vs PUT/PATCH