3.2 身份验证与授权(Authentication & Authorization)


3.2 身份验证与授权(Authentication & Authorization)

本节摘要:登录态怎么保持、页面怎么保护、角色怎么区分?本节讲清 Next.js 的认证方案:Session vs JWT、NextAuth(Auth.js)的接入、中间件 + 服务器组件的双层保护、以及角色权限的落地。

先说结论

阅读完本节,你应当能够:

  1. 理解 Session 与 JWT 两种登录态方案
  2. 用 NextAuth(Auth.js)接入认证
  3. 用中间件保护路由
  4. 在服务器组件/Server Action 中校验登录态
  5. 实现角色权限控制

问题与直觉:认证的本质是"每次请求证明你是谁"

HTTP 无状态——两次请求互不相识。认证系统要解决:登录后,后续每个请求都能识别"你是谁"。两种主流方案:

  • Session:登录成功后,服务器存会话,客户端存 session id(Cookie);每次请求服务器查会话;
  • JWT:登录成功后,服务器签发签名令牌给客户端;客户端带令牌,服务器验签即可(无状态)。

直觉类比:Session 是"会员卡 + 会员档案"(每次出示卡片,店员查档案);JWT 是"盖章通行证"(证件本身自带信息与防伪,验章即通过)。

💡 关键直觉:认证(你是谁)与授权(你能干什么)是两件事。登录态解决前者,角色/权限解决后者。Next.js 项目里两者结合:中间件管"登录没登录"(粗),组件/Server Action 管"有没有权限"(细)。

核心原理:NextAuth(Auth.js)

2.1 安装与基础配置

npm install next-auth
// auth.ts(项目根目录,统一配置) import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ Credentials({ credentials: { email: {}, password: {} }, async authorize(credentials) { // 查库验证用户名密码 const user = await db.user.findUnique({ where: { email: credentials.email as string }, }); if (user && user.password === credentials.password) { return { id: user.id, email: user.email, role: user.role }; } return null; // 验证失败 }, }), ], session: { strategy: "jwt" }, // 用 JWT 会话 callbacks: { jwt({ token, user }) { if (user) token.role = user.role; // 把角色写进 token return token; }, session({ session, token }) { session.user.role = token.role; // 会话里带上角色 return session; }, }, });

2.2 挂载路由与登录表单

// app/api/auth/[...nextauth]/route.ts import { handlers } from "@/auth"; export const { GET, POST } = handlers;
"use client"; import { signIn, signOut } from "next-auth/react"; export function LoginButton() { return ( <button onClick={() => signIn("credentials", { callbackUrl: "/dashboard" })} > 登录 </button> ); }

2.3 读取登录态

// 服务器组件中 import { auth } from "@/auth"; export default async function Dashboard() { const session = await auth(); if (!session) return <p>请先登录</p>; return <h1>欢迎,{session.user.email}</h1>; }

工程实践要点:双层保护 + 角色权限

3.1 中间件保护路由(粗粒度)

// middleware.ts import { auth } from "@/auth"; export default auth((req) => { if (!req.auth && req.nextUrl.pathname.startsWith("/dashboard")) { return Response.redirect(new URL("/login", req.nextUrl)); } }); export const config = { matcher: ["/dashboard/:path*", "/admin/:path*"], };

3.2 组件级校验(细粒度)

// app/admin/page.tsx —— 角色校验 import { auth } from "@/auth"; import { redirect } from "next/navigation"; export default async function AdminPage() { const session = await auth(); if (session?.user?.role !== "admin") { redirect("/dashboard"); // 非管理员拦截 } return <h1>管理后台</h1>; }

3.3 Server Action 中鉴权

"use server"; import { auth } from "@/auth"; export async function deletePost(id: number) { const session = await auth(); if (!session) return { error: "未登录" }; const post = await db.post.findUnique({ where: { id } }); if (post?.authorId !== session.user.id && session.user.role !== "admin") { return { error: "无权删除" }; } await db.post.delete({ where: { id } }); return { success: true }; }

重要Server Action 必须自己鉴权——它直接暴露为网络接口,中间件拦不住直接调用。

3.4 认证方案速查

方案 适合场景 备注
NextAuth/Auth.js 通用项目 支持 OAuth(GitHub/Google)+ 邮箱密码
自建 Session 需要完全控制 数据库存会话
自建 JWT 无状态 API 注意密钥与过期管理
Clerk/Auth0 快速上线 托管服务,省运维

常见误区与排查

误区 现象 正解
只在中间件鉴权 直接调 Action 绕过 每个 Action 内再校验
密码明文存库 泄露风险 用 bcrypt 哈希
token 过期不处理 登录态时有时无 配置 jwt 过期时间
角色只在客户端判断 可被绕过 服务器组件/API 判断
忘配 NEXTAUTH_SECRET 启动报错 环境变量配置密钥

动手演练:完整登录保护

// auth.ts —— 见上方基础配置 // 1. 环境变量 // .env.local: AUTH_SECRET=random-secret // 2. 中间件保护 // middleware.ts —— 见 3.1 双层保护示例 // 3. 登录页 app/login/page.tsx(客户端表单) "use client"; import { signIn } from "next-auth/react"; import { useState } from "react"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); const res = await signIn("credentials", { email, password, redirect: false, }); if (res?.error) setError("邮箱或密码错误"); else window.location.href = "/dashboard"; } return ( <form onSubmit={handleSubmit} className="mx-auto max-w-sm space-y-4"> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> {error && <p className="text-red-500">{error}</p>} <button type="submit">登录</button> </form> ); } // 4. 受保护页面 —— 服务器组件读 session(见 2.3)

完整链路:登录(表单 → signIn)→ session 建立(JWT)→ 中间件拦未登录 → 服务器组件读 session 显示用户 → Server Action 内鉴权防绕过——认证与授权层层设防

要点串联

  • 两种方案:Session(服务端存会话)vs JWT(无状态令牌)。
  • NextAuth/Auth.js:providers + session 配置,支持 OAuth 与凭据登录。
  • 读登录态:服务器组件 await auth() 获取 session。
  • 双层保护:中间件粗拦 + 组件/Server Action 细校验。
  • 角色权限:token 携带 role,服务器侧判断。
  • 安全要点:密码哈希、Action 内鉴权、密钥入环境变量。

深入理解:认证流程与安全实践

登录与鉴权完整流程

密码安全实践

import { hash, compare } from "bcryptjs"; // 注册时:哈希后存储(绝不存明文) const passwordHash = await hash(password, 10); await prisma.user.create({ data: { email, name, passwordHash } }); // 登录时:比对哈希 const match = await compare(inputPassword, user.passwordHash);

为什么哈希:数据库一旦泄露,明文密码直接暴露;哈希后无法反推原文(加盐 + 迭代成本)。绝不自己发明哈希算法,用成熟库。

三层鉴权体系

防线说明:中间件拦"大门"(未登录跳转),服务器组件读 session 渲染个性化内容,Server Action 内部自己鉴权(最容易被忽视但最必要——Action 是直接暴露的网络接口)。三层缺一不可。

常见问题速答

问:JWT 会话和数据库会话怎么选?
JWT 无状态(服务器不存会话,验签即过),适合分布式与 API;数据库会话可控性强(可强制下线),适合需要踢人功能的场景。NextAuth 默认 JWT,够用。

问:登录失败怎么防暴力破解?
限速:同一账号/IP 多次失败后锁定或增加验证码。可用 Upstash Rate Limit(边缘限速)或数据库计数实现。

问:密码重置怎么做?
标准流程:用户提交邮箱 → 生成一次性重置令牌(带过期时间)→ 发邮件 → 用户点击链接设置新密码 → 令牌失效。绝不能在邮件里发密码

问:第三方登录(GitHub/Google)怎么接?
NextAuth 的 providers 里加 GitHub/Google provider,配置 client id/secret 即可——OAuth 流程由 NextAuth 处理,比自己实现安全得多。

问:授权(角色权限)怎么落地?
登录态(认证)与角色(授权)分开:JWT 里带 role,服务器组件/Server Action 里判断 role 是否允许操作。admin 路由配"仅管理员"检查。

一句话总结

认证解决"你是谁",授权解决"你能干什么"——NextAuth 管登录态(Session/JWT),中间件粗拦、组件与 Action 细校验。核心纪律:密码哈希存储、Action 内自证身份、角色判断放服务器侧。认证与授权分开设计,是权限系统的第一原则。

动手练习建议

给项目接上 NextAuth 凭据登录:配置 auth.ts(Credentials provider + 密码哈希校验),挂载 /api/auth 路由,写登录表单,用中间件保护 dashboard 路径。验证:未登录访问跳转登录页、登录后显示用户信息、登出后恢复拦截。

进阶练习:加角色权限——用户表加 role 字段,JWT 回调写入角色,admin 路由做角色校验,一个 Server Action 里同时做"登录检查 + 角色检查"。再测一个真实场景:普通用户调用删除他人数据的 Action,确认被拒绝——权限边界用测试验证是生产级做法。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U