认证与授权模式 authentication-patterns

该技能详细介绍了身份认证和授权的多种核心模式,包括JWT令牌管理、OAuth2流程、RBAC权限控制、会话管理和PKCE机制,适用于软件开发和网络安全领域,帮助实现安全的系统架构。关键词:认证模式、授权模式、JWT、OAuth2、RBAC、PKCE、网络安全、软件开发、安全架构。

身份认证 0 次安装 0 次浏览 更新于 3/8/2026

name: authentication-patterns description: 包括OAuth2、JWT、RBAC、会话管理和PKCE流程的认证和授权模式

认证模式

JWT访问与刷新令牌

import jwt from "jsonwebtoken";

interface TokenPayload {
  sub: string;
  email: string;
  roles: string[];
}

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, email: user.email, roles: user.roles },
    process.env.JWT_SECRET!,
    { expiresIn: "15m", issuer: "auth-service" }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    process.env.REFRESH_SECRET!,
    { expiresIn: "7d", issuer: "auth-service" }
  );

  return { accessToken, refreshToken };
}

function verifyAccessToken(token: string): TokenPayload {
  return jwt.verify(token, process.env.JWT_SECRET!, {
    issuer: "auth-service",
  }) as TokenPayload;
}

短生命期的访问令牌(15分钟)与长生命期的刷新令牌(7天)。将刷新令牌存储在HTTP-only cookie中。

认证中间件

function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "缺少授权头部" });
  }

  try {
    const payload = verifyAccessToken(header.slice(7));
    req.user = payload;
    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: "令牌过期" });
    }
    return res.status(401).json({ error: "无效令牌" });
  }
}

function authorize(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return res.status(401).json({ error: "未认证" });
    if (!roles.some(role => req.user.roles.includes(role))) {
      return res.status(403).json({ error: "权限不足" });
    }
    next();
  };
}

app.get("/admin/users", authenticate, authorize("admin"), listUsers);

OAuth2授权代码流程与PKCE

import crypto from "crypto";

function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString("base64url");
  const challenge = crypto
    .createHash("sha256")
    .update(verifier)
    .digest("base64url");
  return { verifier, challenge };
}

app.get("/auth/login", (req, res) => {
  const { verifier, challenge } = generatePKCE();
  req.session.codeVerifier = verifier;

  const params = new URLSearchParams({
    response_type: "code",
    client_id: process.env.OAUTH_CLIENT_ID!,
    redirect_uri: `${process.env.APP_URL}/auth/callback`,
    scope: "openid profile email",
    code_challenge: challenge,
    code_challenge_method: "S256",
    state: crypto.randomBytes(16).toString("hex"),
  });

  res.redirect(`${process.env.OAUTH_AUTHORIZE_URL}?${params}`);
});

app.get("/auth/callback", async (req, res) => {
  const { code } = req.query;

  const tokenResponse = await fetch(process.env.OAUTH_TOKEN_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: code as string,
      redirect_uri: `${process.env.APP_URL}/auth/callback`,
      client_id: process.env.OAUTH_CLIENT_ID!,
      code_verifier: req.session.codeVerifier,
    }),
  });

  const tokens = await tokenResponse.json();
  const userInfo = jwt.decode(tokens.id_token);

  req.session.user = { id: userInfo.sub, email: userInfo.email };
  res.redirect("/dashboard");
});

RBAC模型

interface Permission {
  resource: string;
  action: "create" | "read" | "update" | "delete";
}

const ROLE_PERMISSIONS: Record<string, Permission[]> = {
  viewer: [
    { resource: "posts", action: "read" },
    { resource: "comments", action: "read" },
  ],
  editor: [
    { resource: "posts", action: "create" },
    { resource: "posts", action: "read" },
    { resource: "posts", action: "update" },
    { resource: "comments", action: "create" },
    { resource: "comments", action: "read" },
  ],
  admin: [
    { resource: "*", action: "create" },
    { resource: "*", action: "read" },
    { resource: "*", action: "update" },
    { resource: "*", action: "delete" },
  ],
};

function hasPermission(roles: string[], resource: string, action: string): boolean {
  return roles.some(role =>
    ROLE_PERMISSIONS[role]?.some(
      p => (p.resource === resource || p.resource === "*") && p.action === action
    )
  );
}

反模式

  • 将JWT存储在localStorage中(易受XSS攻击;应使用HTTP-only cookie)
  • 在多个服务中使用对称密钥用于JWT(应使用RS256与密钥对)
  • 在令牌验证时不验证issaudexp声明
  • 实现自定义密码哈希而不是使用bcrypt/argon2
  • 在基于cookie的认证中缺少CSRF保护
  • 对“用户未找到”与“密码错误”返回不同的错误消息(用户枚举)

检查清单

  • [ ] 访问令牌是短生命期的(15分钟或更短)
  • [ ] 刷新令牌存储在HTTP-only、Secure、SameSite cookie中
  • [ ] 密码使用bcrypt或argon2哈希(从不使用MD5/SHA)
  • [ ] OAuth2 PKCE流程用于公共客户端
  • [ ] RBAC权限在路由和数据访问层都检查
  • [ ] 通过版本计数器或黑名单支持令牌撤销
  • [ ] 为基于cookie的认证启用CSRF保护
  • [ ] 认证错误不揭示用户是否存在