一.session
cookie(会员卡):
- 不超过4KB
- 多个键值对形式
- 域名独立
- 自动发送 未过期的
- 不安全
二.JWT
1.密钥
密钥是什么
密钥就是一个保密的字符串,类似于你的银行卡密码,但通常更长更随机。
在 JWT 的签名算法(如 HS256)中,密钥的作用是:
- 服务端用它对 header 和 payload 进行签名,生成第三段 signature
- 服务端再用同一个密钥去验证签名,判断 token 是否被篡改
密钥只有服务端知道,客户端永远不应该拿到它。
密钥怎么来的
有三种常见来源:
1. 自己手动生成(开发测试用)
bash
bash
# 可以用 Node.js 内置的 crypto 模块生成 node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # 输出类似:a1b2c3d4e5f6...(64位十六进制字符)这条命令会生成一个256 位的随机密钥,适合 HS256 算法。
2. 环境变量配置(推荐用于生产)
javascript
javascript
// .env 文件 JWT_SECRET=my_super_secret_key_2024_x9k2m7 // Node 代码中读取 const jwtSecret = process.env.JWT_SECRET;3. 在线密钥生成器
搜索 "jwt secret generator",网站会自动生成高强度密钥。但注意:不要在不可信的网站上输入你自己的密钥。
密钥的关键注意事项
要点 | 说明 |
|---|---|
长度 | HS256 至少需要 256 位(32字节),HS512 需要 512 位(64字节)。太短容易被暴力破解。 |
随机性 | 必须用加密安全的随机数生成器(如 |
存储 | 永远放在环境变量或密钥管理服务(如 AWS KMS、腾讯云凭据管理系统)中,绝不硬编码在代码里。 |
定期轮换 | 生产环境建议每 90 天更换一次密钥,旧密钥可保留一段时间用于验证未过期的老 token。 |
泄露后果 | 密钥泄露 = 任何人都能用你的密钥签发任意身份的 token,等于整个认证系统崩溃。 |
一段完整示例(Node.js)
javascript
javascript
const jwt = require('jsonwebtoken'); // 1. 生成密钥(实际项目中从环境变量读取) const SECRET_KEY = process.env.JWT_SECRET || 'fallback_dev_key_only'; // 2. 签发 token const token = jwt.sign( { userId: 123, role: 'admin' }, SECRET_KEY, { expiresIn: '24h' } ); // 3. 验证 token try { const decoded = jwt.verify(token, SECRET_KEY); console.log('验证通过,用户ID:', decoded.userId); } catch (err) { console.log('验证失败,token 无效或被篡改'); }一句话总结
密钥就是一个只有服务端知道的随机字符串,用来给 JWT 签名和验签。你可以用
crypto.randomBytes生成它,通过环境变量管理它,绝不能让它离开服务器。
2.JWT身份认证完整流程
好的,下面是 JWT 身份认证的完整流程,并深入到底层生成与验证的细节。
一、整体流程(高视角)
┌─────────┐ ┌──────────────┐ │ 客户端 │ │ 服务端 │ │ (浏览器) │ │ (Node/Java等)│ └────┬────┘ └──────┬───────┘ │ │ │ 1. 登录 (账号+密码) │ │─────────────────────>│ │ │ 2. 验证账号密码 │ │ 3. 生成 JWT │ │ (header.payload.signature) │ 4. 返回 JWT │ │<─────────────────────│ │ │ │ 5. 存储 JWT │ │ (localStorage/cookie) │ │ │ │ 6. 请求API (带JWT) │ │─────────────────────>│ │ │ 7. 提取 JWT │ │ 8. 验证签名 & 过期 │ │ 9. 解析 payload │ 10. 返回资源/数据 │ │<─────────────────────│二、底层逻辑:JWT 是如何生成的?
JWT 由三部分组成,用.连接:header.payload.signature
1. Header(头部)
{ "alg": "HS256", // 签名算法,常用 HS256 (HMAC-SHA256) 或 RS256 "typ": "JWT" // 令牌类型 }- 将 JSON 进行Base64URL 编码(不是普通 Base64,因为 URL 中
+/=有特殊含义,Base64URL 将其替换为-_并去掉=)。 - 得到第一部分字符串,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload(载荷)
{ "sub": "1234567890", // subject,通常是用户ID "name": "John Doe", "iat": 1516239022, // issued at,签发时间戳 "exp": 1516242622 // expiration time,过期时间戳 }- 同样进行Base64URL 编码。
- 注意:Payload 是明文编码,不是加密!任何人只要解码 Base64 就能看到里面的内容,所以绝不要放密码等敏感信息。
3. Signature(签名)
签名是对前两部分的完整性保护,防止篡改。
签名生成公式(以 HS256 为例):
signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key )- 将前两部分的 Base64 字符串用
.拼接。 - 用服务端持有的密钥(secret_key)对该字符串进行 HMAC-SHA256 运算。
- 结果再进行 Base64URL 编码,得到第三段。
最终 JWT 示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c三、底层逻辑:JWT 是如何验证的?
当服务端收到客户端的 JWT 时,执行以下步骤:
- 分割:用
.分割得到 header、payload、signature 三部分。 - 解码 header:Base64URL 解码 header,获取签名算法(如 HS256)。
- 重新计算签名:使用与生成时完全相同的密钥和算法,对
base64(header) + "." + base64(payload)重新计算签名。 - 比较签名:将新计算的签名与 JWT 第三段进行比较。
- 相同:说明内容没有被篡改(因为密钥只有服务端知道,攻击者无法伪造合法签名)。
- 不同:直接拒绝,返回 401。
- 检查有效期:解码 payload,取出
exp(过期时间),与当前时间对比。如果已过期则拒绝。 - 可选检查:检查
iss(签发者)、aud(受众)等声明是否符合预期。
注意:验证过程中不会向数据库发起查询(除非你需要检查用户是否被禁用等额外逻辑),这就是 JWT 无状态的本质。
四、关键安全要点
要素 | 说明 |
|---|---|
密钥保护 | 密钥是核心秘密,必须存储在服务端环境变量或密钥管理服务中,绝不在客户端出现。 |
签名算法选择 | 生产环境推荐RS256(非对称加密),公钥公开,私钥保密,适合多服务间验证。HS256 是对称加密,密钥泄露风险更高。 |
过期时间 | 必须设置 |
Payload 不加密 | 即使有签名,payload 仍是明文,敏感信息(密码、信用卡)不能放进去。 |
HTTPS 强制 | 所有传输必须在 HTTPS 下进行,防止中间人截获 JWT。 |
防重放攻击 | 可使用 |
五、一句话总结
JWT 认证的本质是:服务端用密钥对用户身份信息进行签名,生成一个自包含的令牌;客户端每次请求都带上它,服务端通过重新计算签名来验证令牌的真实性和完整性,从而确认用户身份,全程无需查询会话存储。