姚令武 2025-11-18 19:55 采纳率: 98.7%
浏览 1
已采纳

Next.js Middleware如何防止JWT鉴权绕过?

在使用 Next.js Middleware 进行 JWT 鉴权时,一个常见问题是:如何防止攻击者通过伪造请求头或绕过中间件逻辑来跳过身份验证?例如,直接调用 API 路由或静态资源时,Middleware 是否仍能有效拦截并校验 JWT?若未正确配置匹配器(matcher),可能导致某些路径未被保护。此外,JWT 校验逻辑若仅在客户端执行,易被绕过。如何确保 Middleware 在服务端可靠解析 Authorization 头、验证签名并处理令牌过期,同时防止重放攻击?这是保障安全的关键挑战。
  • 写回答

1条回答 默认 最新

  • 程昱森 2025-11-18 20:05
    关注

    1. 概述:Next.js Middleware 与 JWT 鉴权的基本机制

    在现代全栈应用中,Next.js 的 Middleware 提供了在请求到达目标页面或 API 路由之前进行拦截和处理的能力。结合 JWT(JSON Web Token)进行身份验证时,开发者常将鉴权逻辑置于中间件中,以确保所有受保护资源在服务端完成校验。

    然而,若配置不当,攻击者可能通过以下方式绕过安全机制:

    • 伪造 Authorization 请求头
    • 直接访问未被 matcher 覆盖的 API 路由或静态资源
    • 利用客户端校验漏洞跳过服务端验证
    • 重放有效但已过期或被盗用的 JWT

    因此,构建一个健壮的 JWT 鉴权体系需从匹配规则、服务端解析、签名验证、过期处理及防重放等多维度综合设计。

    2. 匹配器(Matcher)的精确控制

    Next.js 中间件通过 matcher 字段决定哪些路径触发中间件逻辑。若配置不严谨,部分敏感路径可能被遗漏。

    路径模式是否被拦截说明
    /api/protected/*✅ 是明确保护 API 子路径
    /dashboard❌ 否需显式添加或使用正则
    /((?!_next|static).*)✅ 是排除静态资源目录
    /login✅ 可选可放行公共页面
    // middleware.ts
    export const config = {
      matcher: [
        '/((?!_next/static|_next/image|favicon.ico|login).*)',
      ],
    };

    3. 服务端 JWT 解析与 Authorization 头校验

    中间件运行于边缘函数环境(Edge Runtime),必须同步可靠地提取并解析请求头中的 JWT。

    1. 检查是否存在 Authorization
    2. 验证格式是否为 Bearer <token>
    3. 使用 jsonwebtokenjose 库进行解码与签名验证
    4. 拒绝缺失、格式错误或无效签名的请求
    import { NextRequest, NextFetchEvent } from 'next/server';
    import { jwtVerify } from 'jose';
    
    const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET);
    
    async function verifyToken(token: string) {
      try {
        return await jwtVerify(token, SECRET_KEY);
      } catch (err) {
        return null;
      }
    }
    
    export async function middleware(req: NextRequest, ev: NextFetchEvent) {
      const url = req.nextUrl.clone();
      const protectedPaths = /^\/(api\/protected|dashboard)/;
    
      if (!protectedPaths.test(url.pathname)) {
        return NextResponse.next();
      }
    
      const authHeader = req.headers.get('Authorization');
      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return new Response('Unauthorized', { status: 401 });
      }
    
      const token = authHeader.substring(7);
      const decoded = await verifyToken(token);
    
      if (!decoded) {
        return new Response('Invalid or expired token', { status: 403 });
      }
    
      // 继续请求流程
      return NextResponse.next();
    }

    4. 防止重放攻击:引入 jti 与黑名单机制

    即使 JWT 签名有效,攻击者仍可通过截获合法令牌发起重放攻击。解决方案包括:

    • 为每个 JWT 添加唯一标识 jti(JWT ID)
    • 结合短期有效期(如 15 分钟)与刷新令牌机制
    • 将已注销的 jti 记录至 Redis 黑名单,直至自然过期
    graph TD A[收到请求] --> B{包含Authorization?} B -- 否 --> C[返回401] B -- 是 --> D[提取Bearer Token] D --> E[解析JWT获取jti] E --> F{jti在Redis黑名单?} F -- 是 --> G[拒绝访问] F -- 否 --> H[验证签名与exp] H --> I{有效?} I -- 否 --> J[返回403] I -- 是 --> K[放行请求]

    5. 客户端 vs 服务端校验:为何仅前端校验不可信

    许多应用错误地依赖 React 组件内的 JWT 解码显示 UI 状态,但此类逻辑极易被绕过。例如:

    fetch('/api/protected/data', {
      headers: { 'Authorization': 'Bearer fake.token.here' }
    })

    即使前端“未登录”,攻击者仍可通过工具(如 curl、Postman)直接调用后端接口。因此,**所有权限判断必须在服务端中间件或 API 内部重复执行**,杜绝信任客户端输入。

    6. 静态资源与 API 路由的统一防护策略

    Next.js 默认不对 _next/static 等路径执行中间件,除非显式配置。对于需权限控制的私有资源(如用户上传文件),应:

    • 避免存放在 public 目录
    • 通过 API 路由代理下载,经中间件鉴权后再响应文件流
    • 设置 Cache-Control: private 防止 CDN 缓存泄露
    // pages/api/file/[id].ts
    export default async function handler(req, res) {
      const token = req.headers.authorization?.split(' ')[1];
      const isValid = await verifyToken(token);
      if (!isValid) return res.status(403).end();
    
      // 动态读取并返回文件
      const file = await fs.readFile(`/uploads/${req.query.id}`);
      res.setHeader('Content-Type', 'application/octet-stream');
      res.send(file);
    }
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 11月19日
  • 创建了问题 11月18日