在现代 Web 应用中,认证与授权是不可或缺的一部分。Next.js 14 提供了多种方式来实现用户认证和权限控制。今天,我们就来深入探讨如何在 Next.js 14 中实现完整的认证授权系统。
NextAuth.js 集成
1. 基础配置
首先安装 NextAuth.js:
npm install next-auth@beta
配置认证提供者:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
const handler = NextAuth(authConfig);
export { handler as GET, handler as POST };
// auth.config.ts
import type { NextAuthConfig } from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
export const authConfig: NextAuthConfig = {
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) return null;
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
}
return null;
}
})
],
pages: {
signIn: '/login',
error: '/error',
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) return true;
return false;
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
}
}
};
2. 中间件配置
// middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export const { auth: middleware } = NextAuth(authConfig);
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
3. 客户端集成
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
JWT 实现
1. JWT 工具函数
// lib/jwt.ts
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function sign(payload: any): Promise<string> {
const jwt = await new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('24h')
.sign(secret);
return jwt;
}
export async function verify(token: string) {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch (error) {
return null;
}
}
2. JWT 中间件
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verify } from './lib/jwt';
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const payload = await verify(token);
const requestHeaders = new Headers(request.headers);
requestHeaders.set('user', JSON.stringify(payload));
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} catch (error) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
基于角色的访问控制(RBAC)
1. 角色定义
// types/auth.ts
export enum Role {
USER = 'USER',
ADMIN = 'ADMIN',
EDITOR = 'EDITOR'
}
export interface User {
id: string;
email: string;
role: Role;
permissions: string[];
}
// lib/permissions.ts
export const PERMISSIONS = {
'posts:read': 'Read posts',
'posts:write': 'Write posts',
'users:manage': 'Manage users',
} as const;
export const ROLE_PERMISSIONS: Record<Role, string[]> = {
[Role.USER]: ['posts:read'],
[Role.EDITOR]: ['posts:read', 'posts:write'],
[Role.ADMIN]: ['posts:read', 'posts:write', 'users:manage'],
};
2. 权限检查组件
// components/PermissionGate.tsx
'use client';
import { useSession } from 'next-auth/react';
interface PermissionGateProps {
children: React.ReactNode;
permission: string;
fallback?: React.ReactNode;
}
export function PermissionGate({
children,
permission,
fallback = null
}: PermissionGateProps) {
const { data: session } = useSession();
const userPermissions = session?.user?.permissions ?? [];
if (!userPermissions.includes(permission)) {
return fallback;
}
return children;
}
// 使用示例
function AdminPanel() {
return (
<PermissionGate
permission="users:manage"
fallback={<p>无权访问</p>}
>
<div>管理员面板内容</div>
</PermissionGate>
);
}
3. 服务端权限检查
// lib/auth.ts
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
export async function checkPermission(permission: string) {
const session = await getServerSession();
if (!session?.user) {
redirect('/login');
}
const hasPermission = session.user.permissions.includes(permission);
if (!hasPermission) {
throw new Error('Unauthorized');
}
}
// app/admin/page.tsx
export default async function AdminPage() {
await checkPermission('users:manage');
return (
<div>
<h1>管理员页面</h1>
{/* 管理员内容 */}
</div>
);
}
OAuth 2.0 集成
1. 配置 OAuth 提供者
// auth.config.ts
import type { NextAuthConfig } from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
export const authConfig: NextAuthConfig = {
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// 在用户首次登录时创建用户记录
if (account?.provider === 'github' || account?.provider === 'google') {
try {
const existingUser = await prisma.user.findUnique({
where: { email: user.email }
});
if (!existingUser) {
await prisma.user.create({
data: {
email: user.email,
name: user.name,
avatar: user.image,
provider: account.provider,
},
});
}
return true;
} catch (error) {
console.error('Error during OAuth sign in:', error);
return false;
}
}
return true;
},
async session({ session, user }) {
// 添加额外的用户信息到 session
if (session.user) {
session.user.id = user.id;
session.user.role = user.role;
session.user.permissions = user.permissions;
}
return session;
},
},
};
2. OAuth 登录按钮
// components/OAuthButtons.tsx
'use client';
import { signIn } from 'next-auth/react';
export function OAuthButtons() {
return (
<div className="space-y-4">
<button
onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
className="w-full flex items-center justify-center gap-2 bg-gray-900 text-white p-4 rounded-lg"
>
<GithubIcon />
使用 GitHub 登录
</button>
<button
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
className="w-full flex items-center justify-center gap-2 bg-white border border-gray-300 p-4 rounded-lg"
>
<GoogleIcon />
使用 Google 登录
</button>
</div>
);
}
Session 管理
1. Session 存储
// lib/session.ts
import { getServerSession } from 'next-auth';
import { cache } from 'react';
export const getSession = cache(async () => {
const session = await getServerSession();
return session;
});
// 在服务端组件中使用
export default async function ProtectedPage() {
const session = await getSession();
if (!session?.user) {
redirect('/login');
}
return (
<div>
<h1>Welcome {session.user.name}</h1>
</div>
);
}
2. Session 持久化
// lib/auth.ts
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
export const authConfig: NextAuthConfig = {
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database',
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
async session({ session, user }) {
// 从数据库加载最新的用户数据
const freshUser = await prisma.user.findUnique({
where: { id: user.id },
include: {
permissions: true,
settings: true,
},
});
return {
...session,
user: {
...session.user,
...freshUser,
},
};
},
},
};
3. Session 状态管理
// hooks/useAuth.ts
'use client';
import { useSession } from 'next-auth/react';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export function useAuth(requireAuth = true) {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (requireAuth && status === 'unauthenticated') {
router.push('/login');
}
}, [requireAuth, status, router]);
return {
session,
status,
isAuthenticated: status === 'authenticated',
isLoading: status === 'loading',
};
}
// components/AuthGuard.tsx
'use client';
import { useAuth } from '@/hooks/useAuth';
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return null;
}
return children;
}
写在最后
Next.js 14 结合 NextAuth.js 提供了强大而灵活的认证授权解决方案。在实际应用中,需要注意以下几点:
- 选择合适的认证策略(JWT vs Session)
- 实现细粒度的权限控制
- 正确处理 OAuth 集成
- 注意安全性和性能优化
- 提供良好的用户体验
在下一篇文章中,我们将深入探讨 Next.js 14 的性能优化策略。如果你有任何问题或建议,欢迎在评论区讨论!
如果觉得这篇文章对你有帮助,别忘了点个赞 👍