如何使用JWT实现无状态认证?

wen PHP项目 55

本文目录导读:

如何使用JWT实现无状态认证?

  1. JWT 的结构
  2. 无状态认证流程
  3. 具体实现(Node.js + Express 示例)
  4. 关键注意事项
  5. 完整代码仓库示例(FastAPI + Python 版本)

JWT(JSON Web Token)是实现无状态认证的主流方案,所谓“无状态”,是指服务端不需要存储会话信息(如 Session),所有用户身份信息都编码在 Token 中,服务端只需验证 Token 的签名即可。

以下是使用 JWT 实现无状态认证的完整流程、代码示例和最佳实践。


JWT 的结构

一个 JWT 由三部分组成,用 分隔:

header.payload.signature
  • Header:声明类型和签名算法(如 HS256、RS256)。
  • Payload:存放用户信息(如 userId、role)和声明(如过期时间 exp)。
  • Signature:对前两部分进行签名,防止篡改。

无状态认证流程

sequenceDiagram
    participant Client as 客户端
    participant Server as 服务端
    Client->>Server: 1. POST /login (用户名+密码)
    Server->>Server: 2. 验证密码,生成JWT
    Server-->>Client: 3. 返回JWT
    Client->>Server: 4. 请求API + Authorization: Bearer <JWT>
    Server->>Server: 5. 验证JWT签名和过期时间
    Server-->>Client: 6. 返回资源

关键点:

  • 服务端不存 Session,只依赖 JWT 中的信息。
  • JWT 由服务端签发,客户端保存在 localStorage 或 Cookie 中。
  • 每次请求携带 JWT,服务端验证签名后即可识别用户。

具体实现(Node.js + Express 示例)

安装依赖

npm install jsonwebtoken express

登录接口(签发 JWT)

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
// 模拟用户数据库(实际应查 DB)
const users = [{ id: 1, username: 'alice', password: '123456' }];
// 密钥(生产环境用环境变量)
const SECRET_KEY = 'your-256-bit-secret';
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username && u.password === password);
  if (!user) return res.status(401).json({ message: '认证失败' });
  // 生成 JWT,payload 只存必要信息
  const token = jwt.sign(
    { 
      userId: user.id, 
      role: 'user'  // 可以放角色等
    },
    SECRET_KEY,
    { expiresIn: '1h' }  // 设置过期时间
  );
  res.json({ token });
});

认证中间件(验证 JWT)

const authMiddleware = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>
  if (!token) return res.status(401).json({ message: '未提供认证令牌' });
  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    req.user = decoded;  // 将用户信息注入请求对象
    next();
  } catch (err) {
    // token 过期或无效
    return res.status(401).json({ message: '令牌无效或已过期' });
  }
};
// 受保护路由
app.get('/profile', authMiddleware, (req, res) => {
  res.json({ 
    userId: req.user.userId,
    message: '这是受保护的资源'
  });
});

客户端使用(前端示例)

// 登录成功后存储 token
fetch('/login', { method: 'POST', body: JSON.stringify({ username, password }) })
  .then(res => res.json())
  .then(data => {
    localStorage.setItem('token', data.token);
  });
// 请求携带 token
fetch('/profile', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
});

关键注意事项

安全存储 Token

  • 前端:优先放在 httpOnly Cookie 中(可防 XSS 攻击),localStorage。
  • 后端:不要在 JWT 中存放敏感信息(如密码),payload 是 Base64 编码,可被解码。

签名算法选择

  • HS256(对称加密):简单,但密钥需保密,适合单体应用。
  • RS256(非对称加密):使用公钥/私钥对,私钥由认证服务持有,其他服务可用公钥验证,适合微服务架构。

过期与刷新

  • 短期 Token:如 15 分钟,减少泄露风险。
  • Refresh Token:长期有效的令牌(如 7 天),用于获取新的 Access Token。
    // 刷新接口示例
    app.post('/refresh', (req, res) => {
    const refreshToken = req.body.refreshToken;
    // 验证 refreshToken(可以存数据库或使用另一个密钥签名)
    // 返回新的 accessToken
    });

注销问题

  • 无状态 Token 一旦签发,在过期前无法主动失效。
  • 解决方案:
    • 黑名单机制:维护一个撤销列表(有状态,但仅存储已撤销的 Token)。
    • 短期 Token + Refresh Token:Refresh Token 被撤销,强制用户重新登录。

性能与优化

  • 每次验证需解密并校验签名,比 Session 的简单查内存慢。
  • 可使用缓存(如 Redis)缓存公钥或验证结果。

完整代码仓库示例(FastAPI + Python 版本)

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "your-secret-key"
# 创建 JWT
@app.post("/login")
def login(username: str, password: str):
    user = {"id": 1, "username": "alice"}  # 实际需验证密码
    token = jwt.encode({"userId": user["id"], "exp": 3600}, SECRET_KEY, algorithm="HS256")
    return {"access_token": token}
# 验证中间件
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=["HS256"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Invalid token")
@app.get("/protected")
def protected_route(user=Depends(verify_token)):
    return {"user_id": user["userId"]}

特性 传统 Session JWT 无状态
存储 服务端内存/Redis 客户端存储 Token
扩展性 需共享 Session 存储 天然支持跨服务
注销 立即删除 Session 需额外机制
性能 查存储 O(1) 解密验证 O(n)

最佳实践

  • 使用 RS256 签名(微服务场景)。
  • Token 不要超过 8KB(避免 HTTP 头过大)。
  • 敏感操作(支付、改密)需额外验证(如 OTP)。
  • 始终使用 HTTPS 传输。

JWT 的无状态特性使其特别适合分布式系统、移动端和前后端分离场景,但需要配合刷新令牌和黑名单机制解决注销和续期问题。

抱歉,评论功能暂时关闭!