← 返回博客
·安全指南

JWT 认证原理与实战:从原理到代码实现

JWT 是现代 Web 应用最流行的认证方式。本文深入讲解 JWT 的原理、结构、安全最佳实践,并提供 Node.js 和前端完整实现代码。

#JWT#认证#安全#Token

为什么需要 JWT

早期的 Web 认证用 Session + Cookie,逻辑简单,但有个硬伤:服务器要存每份 Session,多台服务器就得做共享存储,扩展性差。

JWT 的思路不一样:服务器签发出去之后就不存了,只负责验证签名是否合法。无状态,好扩展——这也是它流行起来的核心原因。

但 JWT 的坑也比你想象的多。

JWT 结构

JWT 由三部分组成,用 . 分隔:

header.payload.signature

Header(头部)

声明 token 类型和签名算法:

{

"alg": "HS256",

"typ": "JWT"

}

常见算法:HS256(对称加密,密钥只有一个)、RS256(非对称,私钥签名公钥验证,生产推荐)。

Payload(载荷)

存实际数据,**不要存敏感信息**——Base64 是编码不是加密,谁都能解码看。

{

"sub": "1234567890",

"name": "张三",

"iat": 1516239022,

"exp": 1516242622

}

标准字段:iat(签发时间)、exp(过期时间)、sub(主题,一般是用户 ID)。

Signature(签名)

用 Header 里指定的算法,对 header.payload 进行签名。篡改 payload 后签名对不上,token 就失效了。

Node.js 实战:签发与验证

安装依赖

npm install jsonwebtoken

签发 Token

const jwt = require('jsonwebtoken');

const token = jwt.sign(

{

userId: user.id,

role: user.role

},

process.env.JWT_SECRET, // 密钥,存在环境变量里

{

expiresIn: '7d' // 7 天过期

}

);

// token 长这样:

// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInJvbGUiOiJ1c2VyIiwiZXhwIjoxNzA...

验证 Token

try {

const decoded = jwt.verify(token, process.env.JWT_SECRET);

// decoded = { userId: 1, role: 'user', iat: ..., exp: ... }

req.user = decoded;

} catch (err) {

// TokenExpiredError: 过期了

// JsonWebTokenError: 签名不对(被篡改或密钥错误)

return res.status(401).json({ error: 'Invalid token' });

}

Express 中间件完整示例

// middleware/auth.js

const jwt = require('jsonwebtoken');

module.exports = function authMiddleware(req, res, next) {

// 从 Authorization 头里取 token

const authHeader = req.headers['authorization'];

const token = authHeader && authHeader.split(' ')[1]; // "Bearer <token>"

if (!token) {

return res.status(401).json({ error: 'No token provided' });

}

try {

const decoded = jwt.verify(token, process.env.JWT_SECRET);

req.user = decoded;

next();

} catch (err) {

return res.status(403).json({ error: 'Invalid or expired token' });

}

};

// 使用

// app.get('/api/profile', authMiddleware, (req, res) => { ... });

前端怎么存 Token

这是 JWT 用得最乱的地方。常见三种方式:

| 存储方式 | 安全性 | 说明 |

|---------|--------|------|

| localStorage | 差 | 容易被 XSS 读取,不推荐 |

| sessionStorage | 一般 | 关闭标签页就清空,XSS 仍有风险 |

| HttpOnly Cookie | 好 | JS 读不到,防 XSS,但要防 CSRF |

**推荐做法**:用 HttpOnly + SameSite=Strict 的 Cookie 存 token,后端设置:

// Express 设置 Cookie

res.cookie('token', token, {

httpOnly: true, // JS 读不到

secure: true, // 只在 HTTPS 下传输

sameSite: 'strict', // 防 CSRF

maxAge: 7 * 24 * 60 * 60 * 1000 // 7 天

});

刷新 Token(Refresh Token)

Access Token 设短一点(比如 15 分钟),另外发一个有效期长的 Refresh Token,过期时用 Refresh Token 换新的 Access Token。

流程:

  • 登录 → 返回 Access Token(15min)+ Refresh Token(7d,存在 HttpOnly Cookie)
  • 2. Access Token 过期 → 前端调 /refresh 接口,用 Refresh Token 换新的

    3. Refresh Token 也过期 → 用户重新登录

    这样即使 Access Token 被截获,有效期短,损失可控。

    常见坑

    坑1:把敏感信息放 Payload

    Payload 是 Base64 编码,不是加密。有人把用户密码、身份证号放进去——用 atob() 就能解码,跟明文没区别。

    坑2:密钥硬编码

    把 JWT 密钥写死在代码里并推到 GitHub,这种事发生得比你想象的多。一定要用环境变量。

    # .env

    JWT_SECRET=super_random_string_at_least_32_chars

    坑3:Token 无法主动失效

    JWT 是无状态的,签发之后在过期前一直有效。如果用户注销或被盗号,没法让 token 立刻失效。

    **解决方案**:维护一个 Redis 黑名单,验证 token 时先查是否在黑名单里。或者把 token 存在 Redis,注销时删掉,验证时查 Redis 是否存在。

    坑4:用了 `none` 算法

    早期 JWT 库支持 alg: "none",攻击者可以把 Header 改成 none,绕过签名验证。

    **现在的主流库已经修复了这个问题**,但如果你用的库比较老,要手动禁用 none 算法。

    该用 JWT 还是 Session?

    | 场景 | 推荐 |

    |------|------|

    | 单体应用、传统 Web 应用 | Session + Cookie |

    | 微服务、API 服务 | JWT |

    | 需要服务端主动失效(如注销) | Session 或 JWT+Redis |

    | 移动端 App 后端 | JWT |


    总结

    JWT 不是银弹。无状态带来的扩展好处,是以无法主动失效为代价的。用之前想清楚你的场景到底需不需要它。

    > **Pro Tip**: JWT 的 Secret 长度至少 32 字符,用随机生成而不是单词,定期更换。