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

6.7 KiB
Raw Blame History

tags, create time
tags create time
go
frontend
security
authentication
login
encryption
assignment
2026-04-25 14:30

登录安全流程:前端与后端的加密协作

概述

登录是 Web 应用最敏感的操作之一。本文深入探讨前端和后端各自的安全职责,以及 HTTPS 协议下的标准登录认证流程。

核心问题:两端都需要加密吗?

不完全是。 更准确的说法是——传输层加密 + 密码侧哈希。

💡 想一想:如果前端把密码用 SHA-256 哈希后再发送,这样后端收到的是哈希值而不是明文密码,是不是更安全?

答案是否定的。因为哈希后的值本身就是凭证。攻击者截获这个哈希值后,可以直接重放给后端完成登录——哈希值被截获等于密码被截获。所以业界最主流的做法是:前端直接发送明文密码,由 HTTPS 保护传输安全。

登录全流程

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 地址
  • 这是必须的,没有替代方案

第二层:密码存储加密(后端)

谁负责:后端

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 更适合密码哈希

第三层:认证凭据管理(前后端协作)

谁负责:前后端共同

// 后端签发 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)
}
// 前端登录后存储 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 哈希密码
  • 错误信息暴露过多细节

关联笔记