This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/金山办公作业/Week06/登录安全流程.md
T

212 lines
6.7 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: [go, frontend, security, authentication, login, encryption, assignment]
create time: 2026-04-25 14:30
---
# 登录安全流程:前端与后端的加密协作
## 概述
登录是 Web 应用最敏感的操作之一。本文深入探讨前端和后端各自的安全职责,以及 HTTPS 协议下的标准登录认证流程。
## 核心问题:两端都需要加密吗?
**不完全是。** 更准确的说法是——**传输层加密 + 密码侧哈希**。
> 💡 **想一想**:如果前端把密码用 SHA-256 哈希后再发送,这样后端收到的是哈希值而不是明文密码,是不是更安全?
答案是否定的。因为**哈希后的值本身就是凭证**。攻击者截获这个哈希值后,可以直接重放给后端完成登录——哈希值被截获等于密码被截获。所以业界最主流的做法是:**前端直接发送明文密码,由 HTTPS 保护传输安全**。
## 登录全流程
```mermaid
sequenceDiagram
participant U as 用户
participant F as 前端(React)
participant S as 后端(Go)
participant DB as 数据库
U->>F: 输入用户名 + 密码
F->>S: POST /api/login (HTTPS)
Note over S: 传输层 TLS 加密
S->>DB: SELECT * FROM users WHERE username=?
DB-->>S: 用户记录 (hashed_password)
S->>S: bcrypt.Compare(password, stored_hash)
alt 密码正确
S->>S: 生成 JWT Token
S-->>F: {access_token, expires_in}
F->>F: 保存 Token (Memory/LocalStorage)
F-->>U: 跳转首页 ✓
else 密码错误
S-->>F: 401 Unauthorized
F-->>U: 提示"用户名或密码错误"
end
```
## 三层安全模型
登录安全分为三个层次,每层由不同方负责:
### 第一层:传输加密(HTTPS/TLS)
**谁负责**:服务器端配置,前端透明使用
```
HTTP (明文) → HTTPS (TLS 加密)
password = "123456" → TLS 密文传输
```
- 所有请求自动加密,中间人无法窃取
- 前端只需使用 `https://` 前缀的 API 地址
- 这是**必须的**,没有替代方案
### 第二层:密码存储加密(后端)
**谁负责**:后端
```go
package auth
import "golang.org/x/crypto/bcrypt"
// 注册时:密码哈希后存入数据库
func Register(username, password string) error {
hash, err := bcrypt.GenerateFromPassword(
[]byte(password), bcrypt.DefaultCost,
)
// 默认成本因子 = 10(详见 [[金山办公作业/Week06/bcrypt成本因子]])
if err != nil {
return err
}
// 存入数据库的是 hash,不是 password
return db.InsertUser(username, string(hash))
}
// 登录时:比对输入的密码与存储的哈希
func Login(username, password string) (string, error) {
user, err := db.GetUser(username)
if err != nil {
return "", ErrUserNotFound
}
if err := bcrypt.CompareHashAndPassword(
[]byte(user.HashedPassword), []byte(password),
); err != nil {
return "", ErrWrongPassword
}
// 验证通过 → 签发 Token
return generateJWT(user), nil
}
```
默认成本因子 = 10(详见 [[金山办公作业/Week06/bcrypt成本因子]])
**为什么用 bcrypt?**
- 自动加盐,相同密码在不同用户哈希结果不同
- 计算成本可调,抵御暴力破解
- 比 MD5/SHA-256 更适合密码哈希
### 第三层:认证凭据管理(前后端协作)
**谁负责**:前后端共同
```go
// 后端签发 JWT
func generateJWT(userID string, username string) string {
claims := jwt.MapClaims{
"user_id": userID,
"username": username,
"exp": time.Now().Add(2 * time.Hour).Unix(),
"iat": time.Now().Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
```
```typescript
// 前端登录后存储 Token
async function handleLogin(username: string, password: string) {
const resp = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (resp.ok) {
const data = await resp.json();
// 方式1:Memory(更安全,刷新后失效)
sessionStorage.setItem('token', data.access_token);
// 方式2:LocalStorage(持久登录)
localStorage.setItem('token', data.access_token);
router.push('/dashboard');
}
}
```
## 前端是否需要额外加密?
| 方案 | 前端做哈希? | 安全性 | 复杂度 | 推荐度 |
|------|------------|--------|--------|--------|
| **标准方案** ✅ | ❌ 不加密,直接发 | 高(HTTPS 保护) | 低 | ⭐⭐⭐⭐⭐ |
| 前端 SHA-256 | ✅ 哈希后发送 | 中(哈希值可重放) | 中 | ⭐⭐ |
| 客户端密钥派生 | ✅ PBKDF2/Argon2 | 更高 | 高 | ⭐⭐⭐ |
**为什么标准方案最优?**
```
方案A(前端不加密):
前端明文 → HTTPS加密传输 → 后端验证 bcrypt 哈希
✅ 中间人看不到密码
方案B(前端加 SHA-256):
前端 SHA-256(password) → 密文传输 → 后端比对 SHA-256
❌ 中间人截获哈希值 → 直接重放登录(哈希=密码)
```
**结论**:前端额外哈希不仅不能提升安全性,反而引入了"重放攻击"风险。
## 进阶:高安全场景下的前端处理
对于金融类应用等高安全需求场景,可以采用客户端密钥派生方案——前端在发送密码之前,先用后端下发的随机盐值做 PBKDF2 派生,让传输层上永远不出现原始密码。
详细流程请见:[[金山办公作业/Week06/客户端密钥派生]]
## 最佳实践清单
### 前端
✅ **推荐**
- 使用 HTTPS 地址调用 API
- 登录后 Token 存储在内存(sessionStorage)
- 密码输入框使用 `<input type="password">`(防误看)
- 登录失败后清空密码输入框
- 设置合理的 Token 过期时间(2h ~ 24h)
❌ **避免**
- 在 URL 参数中传递密码
- 用 localStorage 存明文密码
- 登录失败后暴露具体原因("用户名不存在" vs "密码错误")——统一提示"用户名或密码错误"
- 前端对密码做 SHA-256 哈希
### 后端
✅ **推荐**
- 使用 bcrypt/argon2 哈希存储密码
- 统一返回模糊错误信息(防止用户名枚举)
- 添加登录频率限制(防暴力破解)
- JWT 设置短期过期 + Refresh Token 机制
- 密钥通过环境变量管理,不写死代码
❌ **避免**
- 明文存储密码
- 使用 MD5/SHA-256 哈希密码
- 错误信息暴露过多细节
## 关联笔记
- [[金山办公作业/Week05/用户认证.md]] - Go 用户认证完整实现
- [[金山办公作业/Week06/客户端密钥派生]] - 客户端密钥派生(盐值挑战)详解
- [[CS/NET/HTTPS]] - HTTPS/TLS 原理
- [[CS/DB/SQL安全]] - 密码安全存储