You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js 13中为API路由和页面实现JWT令牌认证?

Next.js 13 JWT令牌认证分步实现指南

1. 安装依赖

先安装核心依赖和类型定义:

npm install jsonwebtoken bcryptjs cookie
npm install -D @types/jsonwebtoken @types/bcryptjs @types/cookie

2. 配置环境变量

在项目根目录创建.env.local文件,添加以下变量:

JWT_SECRET=your_strong_secret_key_here # 建议用32位以上随机字符串
JWT_EXPIRES_IN=7d # 令牌有效期,比如7天

3. 核心工具函数封装

创建lib/auth.ts文件,封装JWT生成、验证和密码哈希工具:

import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';

// 密码哈希
export async function hashPassword(password: string) {
  return bcrypt.hash(password, 12);
}

// 密码验证
export async function verifyPassword(password: string, hashedPassword: string) {
  return bcrypt.compare(password, hashedPassword);
}

// 生成JWT令牌
export function generateToken(userId: string) {
  return jwt.sign({ userId }, process.env.JWT_SECRET!, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });
}

// 验证JWT令牌
export function verifyToken(token: string) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET!) as { userId: string };
  } catch (error) {
    return null;
  }
}

4. 登录API路由(App Router)

创建app/api/auth/login/route.ts,处理用户登录请求并将JWT存入HttpOnly Cookie:

import { NextResponse } from 'next/server';
import { verifyPassword, generateToken, hashPassword } from '@/lib/auth';
import { cookies } from 'next/headers';

// 模拟数据库用户(实际项目替换为真实数据库查询)
const mockUsers = [
  {
    id: '1',
    email: 'user@example.com',
    passwordHash: await hashPassword('test123'), // 提前哈希好的测试密码
  },
];

export async function POST(request: Request) {
  const { email, password } = await request.json();

  // 查找用户
  const user = mockUsers.find(u => u.email === email);
  if (!user) {
    return NextResponse.json({ message: '用户不存在' }, { status: 401 });
  }

  // 验证密码
  const isValidPassword = await verifyPassword(password, user.passwordHash);
  if (!isValidPassword) {
    return NextResponse.json({ message: '密码错误' }, { status: 401 });
  }

  // 生成令牌
  const token = generateToken(user.id);

  // 设置HttpOnly Cookie(生产环境需开启secure)
  cookies().set('token', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60, // 与JWT有效期一致
    path: '/',
  });

  return NextResponse.json({ message: '登录成功' });
}

5. 全局认证中间件

创建middleware.ts,保护指定路由,未登录用户将被重定向到登录页:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from '@/lib/auth';
import { cookies } from 'next/headers';

export function middleware(request: NextRequest) {
  // 不需要保护的路由(登录页、公共API等)
  const publicPaths = ['/', '/login', '/api/auth/login'];
  const isPublicPath = publicPaths.some(path => request.nextUrl.pathname.startsWith(path));

  if (isPublicPath) {
    return NextResponse.next();
  }

  // 获取Cookie中的令牌
  const token = cookies().get('token')?.value;
  if (!token) {
    // 未登录,重定向到登录页
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 验证令牌
  const decodedToken = verifyToken(token);
  if (!decodedToken) {
    cookies().delete('token');
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 令牌有效,继续请求
  return NextResponse.next();
}

// 指定中间件生效的路由
export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

6. 受保护的服务器组件示例

创建app/dashboard/page.tsx,在服务器组件中获取当前登录用户信息:

import { cookies } from 'next/headers';
import { verifyToken } from '@/lib/auth';

// 模拟根据用户ID获取用户信息的函数
async function getUserById(userId: string) {
  return mockUsers.find(u => u.id === userId);
}

export default async function DashboardPage() {
  const token = cookies().get('token')?.value;
  if (!token) {
    // 理论上中间件已经拦截,这里做双重保险
    return <div>请先登录</div>;
  }

  const decodedToken = verifyToken(token);
  if (!decodedToken) {
    return <div>登录状态无效</div>;
  }

  const user = await getUserById(decodedToken.userId);

  return (
    <div>
      <h1>欢迎回来,{user?.email}</h1>
      <p>这是受保护的仪表盘页面</p>
    </div>
  );
}

7. 退出登录API路由

创建app/api/auth/logout/route.ts,清除令牌Cookie:

import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';

export async function POST() {
  cookies().delete('token');
  return NextResponse.json({ message: '退出成功' });
}

8. Pages Router 适配说明

如果使用Pages Router,核心逻辑一致,差异点:

  • API路由放在pages/api/auth/login.ts,使用req和res对象处理Cookie:
    import type { NextApiRequest, NextApiResponse } from 'next';
    import { verifyPassword, generateToken } from '@/lib/auth';
    
    export default async function handler(req: NextApiRequest, res: NextApiResponse) {
      if (req.method !== 'POST') {
        return res.status(405).json({ message: '不支持的请求方法' });
      }
    
      // 登录逻辑...
    
      res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Secure=${process.env.NODE_ENV === 'production'}; SameSite=Strict; Max-Age=${7*24*60*60}; Path=/`);
      res.status(200).json({ message: '登录成功' });
    }
    
  • 受保护页面使用getServerSideProps获取用户信息:
    export async function getServerSideProps(context: any) {
      const token = context.req.cookies.token;
      if (!token) {
        return {
          redirect: { destination: '/login', permanent: false },
        };
      }
    
      const decodedToken = verifyToken(token);
      if (!decodedToken) {
        return {
          redirect: { destination: '/login', permanent: false },
        };
      }
    
      const user = await getUserById(decodedToken.userId);
    
      return { props: { user } };
    }
    

关键安全注意事项

  • 始终使用HttpOnly Cookie存储JWT,防止XSS攻击
  • 生产环境开启secure Cookie属性(仅HTTPS传输)
  • JWT密钥要足够复杂,避免硬编码,用环境变量管理
  • 密码必须哈希后存储,禁止明文存储
  • 中间件要严格控制受保护路由范围
  • 定期更新JWT密钥,令牌过期时间不宜过长

内容的提问来源于stack exchange,提问作者joyadeep limbu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:35:33