Skip to content
第 161 / 250 章前端⏱ 10 分钟阅读

第 161 章:Middleware 与鉴权

学习目标

  • 掌握 Next.js Middleware 编写
  • 学会基于 JWT 的鉴权
  • 理解 NextAuth.js 集成
  • 学会保护路由

一、Middleware 是什么

在请求到达页面 / API 之前运行的代码,可用于鉴权、重定向、Header 注入。

二、基础 Middleware

2.1 创建 middleware.ts

ts
// middleware.ts(项目根目录,不是 app/ 下)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 1. 检查 token
  const token = request.cookies.get('token')?.value;
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 2. 注入 Header
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-user-id', '123');

  return NextResponse.next({
    request: { headers: requestHeaders },
  });
}

// 3. 配置匹配路径
export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

2.2 常用方法

ts
// 重定向
return NextResponse.redirect(new URL('/login', request.url));

// 重写
return NextResponse.rewrite(new URL('/about', request.url));

// 返回 JSON
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

// 设置 Cookie
const response = NextResponse.next();
response.cookies.set('logged-in', 'true');
return response;

三、JWT 鉴权

3.1 安装

bash
pnpm add jose

3.2 验证 token

ts
// lib/auth.ts
import { jwtVerify, SignJWT } from 'jose';

const secret = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function createToken(payload: { userId: number; role: string }) {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(secret);
}

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload as { userId: number; role: string };
  } catch {
    return null;
  }
}

3.3 在 Middleware 中验证

ts
// middleware.ts
import { verifyToken } from '@/lib/auth';

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  const payload = await verifyToken(token);
  if (!payload) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 注入用户信息
  const headers = new Headers(request.headers);
  headers.set('x-user-id', String(payload.userId));
  headers.set('x-user-role', payload.role);

  return NextResponse.next({ request: { headers } });
}

3.4 在页面 / API 读取

ts
// app/api/profile/route.ts
import { headers } from 'next/headers';

export async function GET() {
  const userId = (await headers()).get('x-user-id');
  // ...
}

四、NextAuth.js(推荐)

完整鉴权方案,支持 OAuth、邮箱、密码等。

4.1 安装

bash
pnpm add next-auth@beta

4.2 配置

ts
// auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isProtected = nextUrl.pathname.startsWith('/dashboard');
      if (isProtected && !isLoggedIn) return false;
      return true;
    },
  },
});

4.3 路由处理

ts
// app/api/auth/[...nextauth]/route.ts
export { GET, POST } from '@/auth';
// 或
import { handlers } from '@/auth';
export const { GET, POST } = handlers;

4.4 中间件

ts
// middleware.ts
export { auth as middleware } from '@/auth';

4.5 在 Server Component 用

tsx
import { auth } from '@/auth';

export default async function Dashboard() {
  const session = await auth();
  if (!session?.user) return null;
  return <h1>欢迎,{session.user.name}</h1>;
}

4.6 登录按钮

tsx
import { signIn, signOut } from '@/auth';

export function SignInButton() {
  return (
    <form action={async () => {
      'use server';
      await signIn('github');
    }}>
      <button>登录</button>
    </form>
  );
}

五、基于角色的权限

ts
// middleware.ts
const ROLE_PERMISSIONS: Record<string, string[]> = {
  admin: ['/admin', '/dashboard'],
  user: ['/dashboard'],
};

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
  if (!token) return NextResponse.redirect(new URL('/login', request.url));

  const payload = await verifyToken(token);
  if (!payload) return NextResponse.redirect(new URL('/login', request.url));

  const allowedPaths = ROLE_PERMISSIONS[payload.role] || [];
  if (!allowedPaths.some(p => request.nextUrl.pathname.startsWith(p))) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  return NextResponse.next();
}

六、登录页示例

tsx
// app/login/page.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const router = useRouter();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });
    if (res.ok) {
      router.push('/dashboard');
    } else {
      alert('登录失败');
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">登录</button>
    </form>
  );
}
ts
// app/api/auth/login/route.ts
export async function POST(request: Request) {
  const { email, password } = await request.json();
  const user = await db.user.findUnique({ where: { email } });
  if (!user || !await verifyPassword(password, user.password)) {
    return Response.json({ error: 'Invalid credentials' }, { status: 401 });
  }

  const token = await createToken({ userId: user.id, role: user.role });
  const response = Response.json({ ok: true });
  response.headers.append(
    'Set-Cookie',
    `token=${token}; HttpOnly; Path=/; Max-Age=604800; SameSite=Lax`
  );
  return response;
}

七、本章小结

概念关键
Middleware请求前置处理
JWTjose 库
NextAuth完整鉴权方案
角色中间件检查

动手练习

  1. 写一个 middleware,保护 /dashboard 路由
  2. 实现 JWT 签发和验证
  3. 集成 NextAuth.js(用 GitHub 登录)
  4. 实现基于角色的访问控制

推荐阅读


下一章:第 162 章:部署到 Vercel

本站基于 VitePress 构建 · 由 Codebook 团队维护