Next.js: 认证与授权
最后更新:2026-08-26
给全栈应用加上认证,就像给办公楼装上门禁系统——谁可以进、进到哪一层,都需要精确控制。
1. 你将学到
- Auth.js v5(NextAuth)对接 Credentials / GitHub / Google 多 Provider
- JWT 与 Database 两种 Session 策略的选型与配置
- Middleware 路由保护与
matcher排除公共路径 - Clerk 第三方认证集成(
<SignIn />/<SignUp />/auth()helper) - RBAC 基于角色的权限控制(admin / editor / viewer)
- 受保护 API Route 的
getToken()验证
2. 一个全栈开发者的真实故事
(1) 痛点:上线前才发现没有认证
Alice 在一家 15 人的 SaaS 创业公司工作,她用 Next.js 16 开发了团队协作平台 "TaskFlow",功能齐全。但在上线前安全审计时,技术主管 Bob 指出:
"你的
/dashboard页面任何人都能访问;API 端点没有 Token 验证;用户数据明文暴露。"
审计报告列出了以下问题:
| 问题 | 影响范围 | 风险等级 |
|---|---|---|
| 无登录页面 | 全部路由 | 🔴 高 |
| API Route 未校验身份 | /api/projects/* |
🔴 高 |
| 无角色区分 | 所有用户看到管理面板 | 🟡 中 |
| Session 无过期 | 登录一次永久有效 | 🔴 高 |
(2) Auth.js + Clerk 的解法
用 Auth.js v5 实现标准认证流程,Clerk 作为零配置备选方案。
TS
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
GitHub,
Google,
Credentials({
credentials: { email: {}, password: {} },
async authorize(credentials) {
const user = { id: '1', name: 'Alice', email: 'alice@taskflow.io', role: 'admin' }
return credentials.email === 'alice@taskflow.io' && credentials.password === 'pass123' ? user : null
}
})
],
callbacks: { session: ({ session, token }) => ({ ...session, user: { ...session.user, role: token.role } }) }
})
(3) 收益
| 维度 | 实施前 | 实施后 |
|---|---|---|
未登录访问 /dashboard |
可访问 | 重定向到登录页 |
| API 端点安全性 | 无校验 | getToken() 验证 JWT |
| 角色控制 | 无 | admin / editor / viewer 三级 |
| Session 有效期 | 永久 | 30 天自动过期 |
| 集成耗时 | — | Auth.js: 2h / Clerk: 30min |
3. Auth.js v5(NextAuth)Provider 集成
(1) Provider 架构
Auth.js v5 的 Provider 抽象层允许你通过统一接口对接多种认证源:
graph TB
A[请求认证] --> B{NextAuth Route Handler}
B --> C[Credentials<br/>邮箱+密码]
B --> D[OAuth<br/>GitHub / Google]
B --> E[其他 Provider<br/>Auth0 / Azure AD]
C --> F[JWT Callback<br/>token + session]
D --> F
E --> F
F --> G[Session 返回客户端]
G --> H[Middleware 校验]
H --> I[受保护页面]
H --> J[公开页面]
style B fill:#cce5ff
style F fill:#d4edda
| Provider 类型 | 实现复杂度 | 用户体验 | 适用场景 |
|---|---|---|---|
| Credentials | 中(需自建登录页) | 标准表单 | 自有账号体系 |
| GitHub OAuth | 低 | 一键登录 | 开发者工具 |
| Google OAuth | 低 | 一键登录 | 面向大众用户 |
| OIDC / SAML | 高 | 企业 SSO | 企业内网 |
(2) 安装与初始化
BASH
npm install next-auth@beta
npx auth secret # 生成 AUTH_SECRET
TS
// auth.ts — 集中认证配置
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! }),
Google({ clientId: process.env.GOOGLE_ID!, clientSecret: process.env.GOOGLE_SECRET! })
]
})
▶ 示例:Credentials Provider 完整配置
TS
// auth.ts — Credentials + OAuth 混合
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import GitHub from 'next-auth/providers/github'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
const { email, password } = credentials as { email: string; password: string }
// 实际项目查询数据库
const user = { id: '1', name: 'Alice', email, role: 'admin' }
if (email === 'admin@taskflow.io' && password === 'admin123') return user
return null
}
}),
GitHub
],
callbacks: {
async jwt({ token, user }) {
if (user) token.role = (user as any).role
return token
},
async session({ session, token }) {
session.user.role = token.role as string
return session
}
}
})
💡 提示:
authorize 函数返回 null 表示登录失败,Auth.js 自动返回 401。
4. Session 管理:JWT vs Database
(1) 两种策略对比
Auth.js v5 支持两种 Session 存储策略:
| 维度 | JWT(默认) | Database |
|---|---|---|
| 存储位置 | Cookie 中加密 JWT | 数据库 Session 表 |
| 查询开销 | 零(无 DB 查询) | 每次请求查一次 DB |
| 失效即时性 | 依赖 JWT 过期时间 | 可即时撤销 |
| 扩展性 | 无需数据库 | 需 Prisma 等 ORM |
| 适用规模 | 中小型应用 | 大型企业应用 |
graph LR
subgraph JWT 模式
A1[登录] --> B1[生成 JWT<br/>含 user + role]
B1 --> C1[写入 Cookie]
C1 --> D1[请求 → middleware<br/>解密 JWT → 校验]
end
subgraph Database 模式
A2[登录] --> B2[创建 Session<br/>写入 DB]
B2 --> C2[Session ID → Cookie]
C2 --> D2[请求 → middleware<br/>查询 DB → 校验]
end
style A1 fill:#d4edda
style A2 fill:#cce5ff
(2) Database Session 配置
TS
// auth.ts — Database Session + Prisma
import NextAuth from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: 'database' },
providers: [GitHub, Google]
})
▶ 示例:获取 Session 并展示用户信息
TSX
// app/dashboard/page.tsx — 服务端获取 Session
import { auth } from '@/auth'
export default async function DashboardPage() {
const session = await auth()
if (!session?.user) return <p>请先登录</p>
return (
<div>
<h1>欢迎回来,{session.user.name}</h1>
<p>邮箱:{session.user.email}</p>
<p>角色:{session.user.role}</p>
<img src={session.user.image!} alt="avatar" width={48} height={48} />
</div>
)
}
💻 输出:
TEXT
📖 仅展示
<h1>欢迎回来,Alice</h1>
<p>邮箱:alice@taskflow.io</p>
<p>角色:admin</p>
5. Middleware 路由保护
(1) matcher 配置模式
middleware.ts 在请求到达页面之前拦截,检查 Session 有效性:
graph TB
A[用户请求 /dashboard/*] --> B{middleware.ts}
B -->|有有效 Session| C[放行 → page.tsx]
B -->|无 Session| D[重定向 /login]
D --> E{公共路径?}
E -->|/api/auth/*| F[放行]
E -->|/_next/*| F
E -->|/favicon.ico| F
style B fill:#fff3cd
style C fill:#d4edda
style D fill:#f8d7da
| matcher 模式 | 匹配路径 | 说明 |
|---|---|---|
/dashboard/:path* |
/dashboard/* |
保护仪表盘 |
/api/projects/:path* |
/api/projects/* |
保护 API |
| `/((?!auth | _next | favicon).*)` |
(2) 完整 Middleware 实现
TS
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'
export default auth((req) => {
const { pathname } = req.nextUrl
const isLoggedIn = !!req.auth
const isPublicPath = pathname.startsWith('/login') ||
pathname.startsWith('/register') ||
pathname.startsWith('/api/auth')
if (!isLoggedIn && !isPublicPath) {
return NextResponse.redirect(new URL('/login', req.url))
}
// RBAC:阻止非 admin 访问 /admin
if (pathname.startsWith('/admin') && req.auth?.user?.role !== 'admin') {
return NextResponse.redirect(new URL('/dashboard', req.url))
}
return NextResponse.next()
})
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|images).*)']
}
▶ 示例:API Route 加入认证保护
TS
// app/api/projects/route.ts — 受保护 API
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'
export async function GET(req: NextRequest) {
const token = await getToken({ req })
if (!token) {
return NextResponse.json({ error: '未登录' }, { status: 401 })
}
// token.role 来自 JWT callback
if (token.role !== 'admin' && token.role !== 'editor') {
return NextResponse.json({ error: '权限不足' }, { status: 403 })
}
return NextResponse.json({ projects: [{ id: 1, name: 'TaskFlow' }] })
}
🔥 易错:
getToken() 需配置 AUTH_SECRET 环境变量,否则返回 null。
6. Clerk 第三方认证
(1) Clerk 特性对比
| 特性 | Auth.js | Clerk |
|---|---|---|
| 安装复杂度 | 中(需配置 Provider) | 低(npm + 环境变量) |
| UI 组件 | 自建登录页 | <SignIn /> / <SignUp /> 开箱即用 |
| 多因素认证 | 需手动集成 | 内置支持 |
| 免费层限制 | 无 | 5,000 MAU 免费 |
| 自定义 UI | 完全自由 | 限制较多 |
(2) Clerk 集成步骤
BASH
npm install @clerk/nextjs
# 在 .env.local 添加:
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
# CLERK_SECRET_KEY=...
TSX
// app/layout.tsx — ClerkProvider 包裹根布局
import { ClerkProvider } from '@clerk/nextjs'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html lang="zh">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
▶ 示例:Clerk 登录页 + 受保护页面
TSX
// app/page.tsx — 登录页
import { SignIn, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
export default function HomePage() {
return (
<div>
<SignedOut>
<SignIn routing="hash" />
</SignedOut>
<SignedIn>
<div>
<UserButton afterSignOutUrl="/" />
<h1>欢迎使用 TaskFlow</h1>
</div>
</SignedIn>
</div>
)
}
TSX
// app/dashboard/page.tsx — Clerk auth() 保护
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const { userId } = auth()
if (!userId) redirect('/sign-in')
return <div>Dashboard — 用户ID: {userId}</div>
}
💡 提示: Clerk 的
auth() 是服务端函数,在 Server Component 中使用不需要 'use client'。
(3) Clerk Middleware
TS
// middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = {
matcher: ['/((?!_next|sign-in|sign-up|favicon.ico).*)']
}
7. RBAC 角色权限控制
(1) 角色模型设计
graph TB
A[用户] --> B{Role}
B --> C[admin<br/>全部权限]
B --> D[editor<br/>读写项目]
B --> E[viewer<br/>只读]
C --> F[创建/删除项目]
C --> G[管理成员]
C --> H[修改设置]
D --> I[编辑任务]
D --> J[添加评论]
E --> K[查看仪表盘]
E --> L[阅读文档]
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#f8d7da
| 角色 | 权限值 | 可访问页面 |
|---|---|---|
| admin | 100 | 全部(含 /admin) |
| editor | 50 | /dashboard、/projects(可编辑) |
| viewer | 20 | /dashboard(只读) |
▶ 示例:RBAC 权限检查组件
TSX
// components/PermissionGuard.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
type Role = 'admin' | 'editor' | 'viewer'
const roleHierarchy: Record<Role, number> = { admin: 100, editor: 50, viewer: 20 }
export async function PermissionGuard({
children,
minRole
}: {
children: React.ReactNode
minRole: Role
}) {
const session = await auth()
const userRole = (session?.user?.role as Role) || 'viewer'
if (roleHierarchy[userRole] < roleHierarchy[minRole]) {
redirect('/dashboard')
}
return <>{children}</>
}
TSX
// app/admin/page.tsx — 使用 PermissionGuard
import { PermissionGuard } from '@/components/PermissionGuard'
export default function AdminPage() {
return (
<PermissionGuard minRole="admin">
<h1>管理控制台</h1>
<p>此页面仅 admin 可见。</p>
</PermissionGuard>
)
}
8. 完整示例:多 Provider 认证 + RBAC 综合实现
TSX
// app/dashboard/layout.tsx — 受保护布局 + RBAC
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { PermissionGuard } from '@/components/PermissionGuard'
export default async function DashboardLayout({
children,
analytics,
team
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
const session = await auth()
if (!session) redirect('/login')
const user = session.user!
return (
<div>
<header>
<h1>TaskFlow</h1>
<p>欢迎, {user.name} ({user.role})</p>
<nav>
<a href="/dashboard">总览</a>
{user.role === 'admin' && <a href="/admin">管理</a>}
<a href="/api/auth/signout">退出</a>
</nav>
</header>
<div style={{ display: 'flex', gap: '2rem' }}>
<main>{children}</main>
<aside>
{analytics}
<PermissionGuard minRole="editor">{team}</PermissionGuard>
</aside>
</div>
</div>
)
}
TS
// app/api/projects/[id]/route.ts — 完整 API 保护
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'
const roles = { admin: 100, editor: 50, viewer: 20 }
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const token = await getToken({ req })
const { id } = await params
if (!token) return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
if (roles[token.role as keyof typeof roles] < 50) {
return NextResponse.json({ error: 'forbidden' }, { status: 403 })
}
// 实际删除项目
return NextResponse.json({ id, deleted: true })
}
💻 输出(未登录访问 DELETE):
JSON
{ "error": "unauthorized" }
💻 输出(viewer 角色访问 DELETE):
JSON
{ "error": "forbidden" }
❓ 常见问题
Q Auth.js v4(NextAuth)和 v5 有什么区别?
A v5 使用 App Router 的 Route Handler(
app/api/auth/[...nextauth]/route.ts),支持 Server Components 中直接 auth() 获取 Session,不再需要 getSession() 和 SessionProvider。Q JWT Session 和 Database Session 应该选哪个?
A 小应用或不需要即时撤销 Session 选 JWT(零 DB 查询)。企业应用需要即时踢人、多设备管理的选 Database。JWT 模式下,撤销 Session 要等到 JWT 过期(默认 30 天)。
Q Clerk 和 Auth.js 什么场景选哪个?
A 需要快速上线、不想自建 UI 的 → Clerk(30 分钟集成)。需要完全自定义 UI、自建账号体系的 → Auth.js。Clerk 免费层上限 5,000 MAU,超出后 €25/月。
Q Middleware 里
auth() 和 getToken() 有什么区别?A
auth() 是 Auth.js v5 的包装函数,返回完整的 Session 对象。getToken() 来自 next-auth/jwt,只解析 JWT Token,性能更好。在 Middleware 中推荐 auth() 简洁;在 API Route 中推荐 getToken() 轻量。Q 如何保护静态导出的页面?
A 使用
output: 'export' 的静态站点无法使用 Middleware(需要 Node.js 运行时)。此时应在 Client Component 中用 useSession() Hook 检查登录状态,或用 Clerk 的 <SignedIn> / <SignedOut> 条件渲染。📖 小节
- Auth.js v5 使用 Route Handler 模式,
auth()可在 Server Component 中直接获取 Session - Provider 支持 Credentials / GitHub / Google 等多源,通过 Callback 机制注入自定义数据
- JWT Session 零 DB 查询适合小应用,Database Session 支持即时撤销适合企业
- Middleware 通过
matcher配置保护路径,反向匹配排除公共资源 - Clerk 提供开箱即用 UI 组件,适合快速原型,免费层 5,000 MAU
- RBAC 通过角色值比较实现权限控制,结合 PermissionGuard 组件声明式使用
📝 作业
-
基础题(⭐):创建一个新的 Next.js 项目,集成 Auth.js v5 的 GitHub Provider,在
/dashboard页面展示用户头像和邮箱。 -
进阶题(⭐⭐):在 Middleware 中添加 RBAC 逻辑:
admin可访问/admin/*,editor可访问/projects/*编辑接口,其他角色越权时返回 403 页面。 -
挑战题(⭐⭐⭐):混合使用 Auth.js(Credentials Provider)和 Prisma:用户在登录页输入邮箱密码 →
authorize查询数据库校验 → JWT 中写入role和orgId→ Middleware 根据orgId路由到对应组织的工作区。