Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

身份与安全 / Identity & Security

1. 认证鉴权——JWT 过期了,用户怎么办 / Authentication, Authorization, and JWT Expiration

2. 安全加固——你的 API 被黑客盯上了 / API Security Hardening Against Real Attacks

3. JWT 完全指南 / A Complete Guide to JSON Web Tokens

4. Session + Cookie 完全指南 / A Complete Guide to Sessions and Cookies

本页目录

JWT 完全指南 / A Complete Guide to JSON Web Tokens ​

解释 JWT 是什么,为什么需要它,以及如何使用。


1. 为什么需要 JWT? ​

在开发 Web 应用时,你经常会遇到这样的场景:

typescript
// 用户登录后,服务器需要知道"你是谁"
if (用户已登录) {
  return "你的私密数据"
}
1
2
3
4

传统的解决方案是Session(会话):

浏览器                    服务器
  │                         │
  │──── 登录请求 ──────────►│
  │◄─── 设置 Cookie ────────│
  │                         │
  │──── 请求(带 Cookie)─►│ 服务器读取 Session
  │◄─── 返回数据 ──────────│
1
2
3
4
5
6
7

Session 的问题:

  1. 服务器压力大:每个登录用户都要在服务器存储 Session
  2. 分布式困难:多台服务器需要共享 Session
  3. 移动端不友好:移动端处理 Cookie 不方便

JWT 就是来解决这些问题的。


2. JWT 是什么? ​

JWT(JSON Web Token) 是一种令牌格式,可以把用户信息编码成一个字符串,服务器生成后交给客户端,客户端每次请求带上这个令牌,服务器解析后就知道"你是谁"了。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1

这个字符串分为三部分,用 . 分隔:

  • Header(头部):声明令牌类型和签名算法
  • Payload(载荷):存放实际数据
  • Signature(签名):验证令牌是否被篡改

3. JWT 的结构 ​

Header.Payload.Signature
1

3.1 Header(头部) ​

json
{
  "alg": "HS256",  // 签名算法:HMAC SHA-256
  "typ": "JWT"     // 类型
}
1
2
3
4

Base64 编码后:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

3.2 Payload(载荷) ​

存放实际需要传递的数据,标准声明(可选):

声明含义
iss签发者
sub主题(用户 ID)
aud受众
exp过期时间
nbf生效时间
iat签发时间
jtiJWT ID(唯一标识)

自定义声明(想放什么放什么):

json
{
  "sub": "1234567890",      // 用户 ID
  "name": "John Doe",       // 用户名
  "role": "admin",          // 角色
  "iat": 1516239022         // 签发时间
}
1
2
3
4
5
6

Base64 编码后:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

3.3 Signature(签名) ​

对 Header 和 Payload 进行签名,防止数据被篡改:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  SECRET_KEY
)
1
2
3
4

4. 在代码中使用 JWT ​

4.1 安装依赖 ​

bash
npm install jsonwebtoken
npm install -D @types/jsonwebtoken
1
2

4.2 生成 JWT(后端) ​

typescript
// 📁 backend/src/utils/jwt.ts
import jwt from "jsonwebtoken"

const secret = process.env.JWT_SECRET || "your-secret-key"  // 密钥,要保管好!

// 生成令牌
const token = jwt.sign(
  {
    sub: "1234567890",     // 用户 ID
    name: "John Doe",      // 用户名
    role: "admin"          // 角色
  },
  secret,
  {
    expiresIn: "7d"        // 有效期:7 天
  }
)

console.log(token)
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA0...(后续省略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4.3 验证 JWT(后端) ​

typescript
// 📁 backend/src/utils/jwt.ts
import jwt from "jsonwebtoken"

const secret = process.env.JWT_SECRET || "your-secret-key"

try {
  const decoded = jwt.verify(token, secret)
  
  console.log(decoded)
  // {
  //   sub: '1234567890',
  //   name: 'John Doe',
  //   role: 'admin',
  //   iat: 1704...,
  //   exp: 1704...
  // }
} catch (err) {
  // 令牌无效(过期、被篡改)
  console.log("验证失败:", err.message)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4.4 提取 Payload(不验证) ​

typescript
// 📁 backend/src/utils/jwt.ts
// 只提取 Payload,不验证签名(不安全,仅用于调试)
const payload = jwt.decode(token)

console.log(payload.sub)   // 1234567890
console.log(payload.name)  // John Doe
1
2
3
4
5
6

5. JWT 的使用流程 ​

┌─────────┐                              ┌─────────┐
│  客户端  │                              │  服务器  │
└────┬────┘                              └────┬────┘
     │                                        │
     │──── 1. 登录请求(用户名密码) ─────────►│
     │◄─── 2. 返回 JWT 令牌 ──────────────────│
     │                                        │
     │──── 3. 请求(带 JWT) ────────────────►│
     │     Authorization: Bearer <token>     │
     │◄─── 4. 返回数据 ──────────────────────│
     │                                        │
     │──── 5. 请求(带 JWT) ────────────────►│
     │◄─── 6. 返回数据 ──────────────────────│
1
2
3
4
5
6
7
8
9
10
11
12
13

5.1 客户端存储 JWT(前端) ​

方式一:localStorage

typescript
// 📁 frontend/src/utils/auth.ts
// 登录成功后存储
localStorage.setItem("token", token)

// 发送请求时带上
const token = localStorage.getItem("token")
fetch("/api/data", {
  headers: {
    "Authorization": `Bearer ${token}`
  }
})
1
2
3
4
5
6
7
8
9
10
11

方式二:Cookie(更安全)

typescript
// 📁 frontend/src/api/axios.ts
// 服务器设置 HttpOnly Cookie
// 客户端自动发送,无需手动处理
1
2
3

5.2 服务器验证 JWT(后端) ​

typescript
// 📁 backend/src/routes/data.ts
import express from "express"
import jwt from "jsonwebtoken"

const router = express.Router()
const secret = process.env.JWT_SECRET || "your-secret-key"

router.get("/data", (req, res) => {
  const authHeader = req.headers.authorization
  
  if (!authHeader) {
    return res.status(401).json({ error: "未登录" })
  }
  
  const token = authHeader.replace("Bearer ", "")
  
  try {
    const decoded = jwt.verify(token, secret)
    // 验证通过,获取用户信息
    res.json({ 
      userId: decoded.sub,
      name: decoded.name,
      data: "你的私密数据"
    })
  } catch (err) {
    res.status(401).json({ error: "令牌无效" })
  }
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

6. JWT 的优势 ​

特性说明
无状态服务器不需要存储 Session,只验证令牌即可
跨域支持适合前后端分离、移动端、分布式系统
标准格式JSON 格式,跨语言兼容
可验证签名机制保证不被篡改

7. JWT 的安全问题 ​

7.1 不要在 Payload 存放敏感信息 ​

typescript
// 📁 backend/src/utils/jwt.ts
// ❌ 错误:密码放在 JWT 里
jwt.sign({ userId: 1, password: "123456" }, secret)

// ✅ 正确:只放不敏感的信息
jwt.sign({ userId: 1 }, secret)
1
2
3
4
5
6

7.2 密钥要保管好 ​

typescript
// 📁 backend/src/utils/jwt.ts
// ❌ 错误:硬编码在代码里
const secret = "my-secret-key"

// ✅ 正确:从环境变量读取
const secret = process.env.JWT_SECRET
1
2
3
4
5
6

7.3 设置合理的过期时间 ​

typescript
// 📁 backend/src/utils/jwt.ts
// ✅ 合理:7 天
jwt.sign(payload, secret, { expiresIn: "7d" })

// ✅ 更安全:访问令牌短一些,刷新令牌长一些
jwt.sign(payload, secret, { expiresIn: "1h" })  // 访问令牌 1 小时
1
2
3
4
5
6

7.4 使用 HTTPS ​

生产环境必须使用 HTTPS,防止令牌被截获。


8. 完整的项目结构示例 ​

my-app/
├── .env                          ← 【JWT 密钥】(不提交到 Git)
├── .env.example                  ← 【模板】(提交到 Git)
├── frontend/                     ← 【前端】
│   ├── src/
│   │   ├── utils/
│   │   │   └── auth.ts           ← 【登录、存取 Token】
│   │   ├── api/
│   │   │   └── axios.ts          ← 【请求拦截器】
│   │   └── stores/
│   │       └── user.ts           ← 【Pinia Store】
│   └── package.json
├── backend/                      ← 【后端】
│   ├── src/
│   │   ├── utils/
│   │   │   └── jwt.ts            ← 【JWT 工具函数】
│   │   ├── middleware/
│   │   │   └── auth.ts           ← 【验证中间件】
│   │   ├── routes/
│   │   │   ├── auth.ts           ← 【登录接口】
│   │   │   └── data.ts           ← 【需要验证的接口】
│   │   └── index.ts              ← 【入口文件】
│   └── package.json
└── package.json                  ← 【根目录】(如有 monorepo)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

8.1 封装 JWT 工具(后端) ​

typescript
// 📁 backend/src/utils/jwt.ts
import jwt from "jsonwebtoken"

const secret = process.env.JWT_SECRET || "default-secret"

export interface TokenPayload {
  sub: string      // 用户 ID
  name: string     // 用户名
  role: string     // 角色
}

export function generateToken(payload: TokenPayload): string {
  return jwt.sign(payload, secret, { expiresIn: "7d" })
}

export function verifyToken(token: string): TokenPayload {
  return jwt.verify(token, secret) as TokenPayload
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

8.2 封装验证中间件(后端) ​

typescript
// 📁 backend/src/middleware/auth.ts
import { Request, Response, NextFunction } from "express"
import { verifyToken } from "../utils/jwt"

export function authMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const authHeader = req.headers.authorization
  
  if (!authHeader) {
    return res.status(401).json({ error: "未提供令牌" })
  }
  
  const token = authHeader.replace("Bearer ", "")
  
  try {
    const decoded = verifyToken(token)
    ;(req as any).user = decoded  // 挂载到请求对象上
    next()
  } catch (err) {
    return res.status(401).json({ error: "令牌无效或已过期" })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

8.3 在路由中使用(后端) ​

typescript
// 📁 backend/src/routes/auth.ts
import express from "express"
import { generateToken } from "../utils/jwt"

const router = express.Router()

// 登录
router.post("/login", (req, res) => {
  const { username, password } = req.body
  
  // 这里应该验证用户名密码
  const user = { id: "123", name: username, role: "user" }
  
  const token = generateToken({
    sub: user.id,
    name: user.name,
    role: user.role
  })
  
  res.json({ token })
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typescript
// 📁 backend/src/routes/data.ts
import express from "express"
import { authMiddleware } from "../middleware/auth"

const router = express.Router()

// 需要登录的接口
router.get("/profile", authMiddleware, (req, res) => {
  const user = (req as any).user
  res.json({ 
    id: user.sub,
    name: user.name,
    role: user.role
  })
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

8.4 前端存储 Token(前端) ​

typescript
// 📁 frontend/src/utils/auth.ts
// 登录成功后存储
export function saveToken(token: string) {
  localStorage.setItem("token", token)
}

// 获取 Token
export function getToken(): string | null {
  return localStorage.getItem("token")
}

// 清除 Token(登出)
export function clearToken() {
  localStorage.removeItem("token")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typescript
// 📁 frontend/src/api/axios.ts
import axios from "axios"
import { getToken } from "../utils/auth"

const api = axios.create({
  baseURL: "/api"
})

// 请求拦截器:自动带上 Token
api.interceptors.request.use((config) => {
  const token = getToken()
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

export default api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript
// 📁 frontend/src/stores/user.ts
import { defineStore } from "pinia"
import { saveToken, getToken, clearToken } from "../utils/auth"

export const useUserStore = defineStore("user", {
  state: () => ({
    token: getToken() || "",
    userInfo: null as any
  }),
  actions: {
    setToken(token: string) {
      this.token = token
      saveToken(token)
    },
    logout() {
      this.token = ""
      this.userInfo = null
      clearToken()
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

9. 常见问题 ​

Q1:JWT 令牌存在哪里? ​

  • localStorage:方便,但容易受到 XSS 攻击
  • Cookie(HttpOnly):更安全,但需要服务器支持
  • 移动端通常存在本地存储

Q2:JWT 令牌过期了怎么办? ​

  • 方案一:刷新令牌(Refresh Token)
  • 方案二:让用户重新登录

Q3:JWT 可以登出/失效吗? ​

JWT 是无状态的,服务器不存储。解决方案:

  • 使用黑名单(Redis)存储已失效的 JWT
  • 使用短期令牌 + 刷新机制
  • 颁发时间 iat + 服务端记录用户最近活跃时间

Q4:JWT 和 Session 选哪个? ​

场景推荐
前后端分离、移动端JWT
传统 Web 应用Session
需要跨域、分布式JWT

10. 总结 ​

概念说明
JWTJSON Web Token,一种令牌格式
Header头部,声明算法和类型
Payload载荷,存放实际数据
Signature签名,验证令牌完整性
sign()生成 JWT
verify()验证 JWT
expiresIn设置过期时间

最佳实践:

  1. JWT 存在 Cookie(HttpOnly)里,更安全
  2. 密钥从环境变量读取,不要硬编码
  3. 设置合理的过期时间
  4. 不要在 Payload 里放敏感信息
  5. 生产环境务必使用 HTTPS

最后更新于:

Pager
上一篇2. 安全加固——你的 API 被黑客盯上了 / API Security Hardening Against Real Attacks
下一篇4. Session + Cookie 完全指南 / A Complete Guide to Sessions and Cookies

持续记录,持续成长

Copyright © Tidenflow