当前位置: 首页 > news >正文

Next.js + Prisma + Auth.js 实现完整的认证方案

前言

在现代 Web 应用中,用户认证是一个基础且重要的功能。本文将介绍如何使用 Next.js + Prisma + Auth.js 实现一个完整的认证方案。这个方案既安全又灵活,能满足大多数项目需求。

技术栈选择

  • • Next.js: React 全栈框架,提供了服务端渲染和 API 路由
  • • Prisma: 现代数据库 ORM,简化数据库操作
  • • Auth.js: 专注于认证的库,支持多种认证方式

这个组合的优势在于:Next.js 的全栈特性让前后端代码共处一个项目,Prisma 的类型安全让数据库操作更可靠,Auth.js 则提供了完整的认证解决方案。

实现步骤

1. 数据库设计

为什么选择这样的用户表结构?

用户表(User)是认证系统的核心,我们的设计考虑了以下几个关键点:

  • • id: 使用 cuid() 而不是自增ID,因为它提供了更好的安全性和分布式唯一性
  • • email: 设为唯一索引,作为用户的主要标识符
  • • password: 只存储加密后的密码哈希
  • • role: 用于基本的权限控制
  • • emailVerified: 支持邮箱验证功能
  • • createdAt/updatedAt: 用于审计和数据跟踪
// schema.prisma
datasource db {provider = "postgresql" // 或 "mysql"url      = env("DATABASE_URL")
}generator client {provider = "prisma-client-js"
}model User {id            String    @id @default(cuid())name          String?email         String    @uniquepassword      Stringrole          String    @default("user")emailVerified DateTime?image         String?createdAt     DateTime  @default(now())updatedAt     DateTime  @updatedAt
}

2. Auth.js 配置

Auth.js 工作原理

Auth.js(原 NextAuth.js)提供了一个完整的认证框架,它的工作流程是:

  1. 1. 用户提交登录请求
  2. 2. Auth.js 通过配置的 Provider 验证凭据
  3. 3. 验证成功后创建 JWT token
  4. 4. 将 token 存储在 cookie 中
  5. 5. 后续请求通过 middleware 验证 token

下面是具体配置及解释:

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';const prisma = new PrismaClient();export const authOptions = {providers: [CredentialsProvider({name: 'Credentials',credentials: {email: { label: "邮箱", type: "email" },password: { label: "密码", type: "password" }},async authorize(credentials) {if (!credentials?.email || !credentials?.password) {throw new Error('请输入邮箱和密码');}const user = await prisma.user.findUnique({where: { email: credentials.email }});if (!user) {throw new Error('用户不存在');}const isPasswordValid = await bcrypt.compare(credentials.password,user.password);if (!isPasswordValid) {throw new Error('密码错误');}return {id: user.id,email: user.email,name: user.name,role: user.role,};}})],callbacks: {async jwt({ token, user }) {if (user) {token.role = user.role;}return token;},async session({ session, token }) {session.user.role = token.role;return session;}},pages: {signIn: '/login',error: '/auth/error',},session: {strategy: "jwt",maxAge: 30 * 24 * 60 * 60, // 30 天},secret: process.env.NEXTAUTH_SECRET,
};const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
配置详解:
  1. 1. Providers配置
  • • CredentialsProvider: 处理用户名密码登录
  • • 可以添加其他提供商如 Google, GitHub 等
  1. 1. Callbacks配置
  • • jwt: 自定义 JWT token 内容
  • • session: 定制返回给客户端的会话信息
  1. 1. Session配置
  • • strategy: "jwt" 使用无状态JWT存储
  • • maxAge: 控制会话有效期

3. API 实现

注册流程设计

注册API需要处理的关键点:

  1. 1. 输入验证
  2. 2. 密码加密
  3. 3. 用户查重
  4. 4. 安全响应
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import { NextResponse } from 'next/server';const prisma = new PrismaClient();export async function POST(request) {try {const { email, password, name } = await request.json();// 验证输入if (!email || !password) {return NextResponse.json({ error: '请提供所有必需的信息' },{ status: 400 });}// 检查用户是否已存在const existingUser = await prisma.user.findUnique({where: { email }});if (existingUser) {return NextResponse.json({ error: '该邮箱已被注册' },{ status: 400 });}// 加密密码const hashedPassword = await bcrypt.hash(password, 10);// 创建新用户const user = await prisma.user.create({data: {email,password: hashedPassword,name,}});return NextResponse.json({ message: '注册成功', user: { id: user.id, email: user.email, name: user.name } },{ status: 201 });} catch (error) {return NextResponse.json({ error: '注册失败' },{ status: 500 });}
}
安全考虑
  • • 密码通过 bcrypt 加密,使用10轮加密(性能和安全的平衡)
  • • 永不返回密码相关信息
  • • 统一的错误处理避免信息泄露

4. 中间件实现

中间件的作用

Next.js 中间件在请求到达页面之前执行,用于:

  1. 1. 路由保护
  2. 2. 权限验证
  3. 3. 重定向处理
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";export default withAuth(function middleware(req) {// 获取当前路径和用户角色const path = req.nextUrl.pathname;const token = req.nextauth.token;// 管理员路由保护if (path.startsWith("/admin") && token?.role !== "admin") {return NextResponse.redirect(new URL("/login", req.url));}return NextResponse.next();},{callbacks: {authorized: ({ token }) => !!token},}
);// 配置需要保护的路由
export const config = {matcher: ["/dashboard/:path*", "/admin/:path*", "/profile/:path*"]
};
工作流程
  1. 1. 请求进入
  2. 2. 中间件检查 token
  3. 3. 根据路由规则决定:
    • • 允许访问
    • • 重定向到登录
    • • 返回错误

5. 前端实现

登录组件设计考虑

登录表单需要处理:

  1. 1. 表单状态管理
  2. 2. 错误处理
  3. 3. 加载状态
  4. 4. 成功后跳转
'use client';import { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';export default function LoginPage() {const router = useRouter();const [error, setError] = useState('');const [loading, setLoading] = useState(false);async function handleSubmit(e) {e.preventDefault();setLoading(true);setError('');const formData = new FormData(e.currentTarget);const email = formData.get('email');const password = formData.get('password');try {const result = await signIn('credentials', {email,password,redirect: false,});if (result?.error) {setError(result.error);} else {router.push('/dashboard');router.refresh();}} catch (error) {setError('登录过程中出现错误');} finally {setLoading(false);}}return (<div className="min-h-screen flex items-center justify-center"><form onSubmit={handleSubmit} className="space-y-4 w-full max-w-md"><h1 className="text-2xl font-bold text-center">登录</h1>{error && (<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">{error}</div>)}<div><label htmlFor="email" className="block text-sm font-medium">邮箱</label><inputtype="email"name="email"requiredclassName="mt-1 block w-full rounded-md border-gray-300 shadow-sm"/></div><div><label htmlFor="password" className="block text-sm font-medium">密码</label><inputtype="password"name="password"requiredclassName="mt-1 block w-full rounded-md border-gray-300 shadow-sm"/></div><buttontype="submit"disabled={loading}className="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">{loading ? '登录中...' : '登录'}</button></form></div>);
}
用户体验优化
  • • 表单验证即时反馈
  • • 登录状态清晰展示
  • • 错误信息友好展示
  • • 防止重复提交

关键实现细节

密码处理

1. 密码加密存储

使用 bcrypt 进行密码加密是业界标准做法:

// src/lib/auth/password.js
import bcrypt from 'bcryptjs';export async function hashPassword(password) {// 使用10轮加密,在安全性和性能间取得平衡return await bcrypt.hash(password, 10);
}export async function verifyPassword(password, hashedPassword) {return await bcrypt.compare(password, hashedPassword);
}
2. 密码安全策略

实现密码强度验证:

// src/lib/auth/validation.js
export function validatePassword(password) {const minLength = 8;const hasUpperCase = /[A-Z]/.test(password);const hasLowerCase = /[a-z]/.test(password);const hasNumbers = /\d/.test(password);const hasSpecialChar = /[!@#$%^&*]/.test(password);const errors = [];if (password.length < minLength) {errors.push('密码长度至少8位');}if (!hasUpperCase) errors.push('需包含大写字母');if (!hasLowerCase) errors.push('需包含小写字母');if (!hasNumbers) errors.push('需包含数字');if (!hasSpecialChar) errors.push('需包含特殊字符');return {isValid: errors.length === 0,errors};
}

会话管理

1. JWT 配置

配置 JWT 令牌的生成和验证:

// src/app/api/auth/[...nextauth]/route.js 中的配置补充
export const authOptions = {// ... 其他配置jwt: {maxAge: 60 * 60 * 24 * 30, // 30天async encode({ secret, token }) {// 可以添加自定义加密逻辑return jwt.sign(token, secret);},async decode({ secret, token }) {// 可以添加自定义解密逻辑return jwt.verify(token, secret);}},// 添加会话刷新逻辑callbacks: {async jwt({ token, user, account }) {// 初次登录if (account && user) {return {...token,accessToken: account.access_token,refreshToken: account.refresh_token,};}// 检查token是否过期if (Date.now() < token.accessTokenExpires) {return token;}// 刷新tokenreturn refreshAccessToken(token);}}
};
2. 会话刷新实现
// src/lib/auth/session.js
async function refreshAccessToken(token) {try {// 实现token刷新逻辑const response = await fetch('/api/auth/refresh', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({refreshToken: token.refreshToken,}),});const refreshedTokens = await response.json();if (!response.ok) {throw refreshedTokens;}return {...token,accessToken: refreshedTokens.access_token,refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,};} catch (error) {return {...token,error: "RefreshAccessTokenError",};}
}

安全考虑

1. CSRF 防护

实现 CSRF token 验证:

// src/middleware.ts 中添加CSRF检查
import { csrf } from '@/lib/csrf';export async function middleware(request: NextRequest) {// 检查是否是修改数据的请求if (['POST', 'PUT', 'DELETE'].includes(request.method)) {const csrfToken = request.headers.get('X-CSRF-Token');const cookieToken = request.cookies.get('csrf-token');if (!csrfToken || !cookieToken || csrfToken !== cookieToken) {return new NextResponse(JSON.stringify({ message: 'Invalid CSRF token' }),{ status: 403 });}}return NextResponse.next();
}
2. XSS 防护

实现内容安全策略(CSP):

// src/middleware.ts 中添加CSP头
export function middleware(request: NextRequest) {const response = NextResponse.next();// 设置CSP头response.headers.set('Content-Security-Policy',"default-src 'self'; " +"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +"style-src 'self' 'unsafe-inline'; " +"img-src 'self' data: https:; " +"font-src 'self';");return response;
}
3. 登录频率限制

实现请求限流:

// src/lib/auth/rateLimit.js
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';export const loginLimiter = rateLimit({store: new RedisStore({// Redis配置}),windowMs: 15 * 60 * 1000, // 15分钟max: 5, // 限制5次尝试message: {error: '登录尝试次数过多,请15分钟后再试'}
});// 在登录API中使用
export async function POST(request) {try {await loginLimiter(request);// 处理登录逻辑} catch (error) {if (error.message.includes('登录尝试次数过多')) {return NextResponse.json({ error: error.message }, { status: 429 });}throw error;}
}

错误处理

统一错误处理器

创建全局错误处理:

// src/lib/errors/handler.js
export class AuthError extends Error {constructor(message, code = 'AUTH_ERROR') {super(message);this.code = code;}
}export function handleAuthError(error) {console.error(`认证错误: ${error.message}`);// 根据错误类型返回适当的响应const errorResponses = {'AUTH_ERROR': { status: 401, message: '认证失败' },'INVALID_CREDENTIALS': { status: 401, message: '用户名或密码错误' },'USER_NOT_FOUND': { status: 404, message: '用户不存在' },'RATE_LIMIT': { status: 429, message: '请求过于频繁' },// ... 其他错误类型};const response = errorResponses[error.code] || {status: 500,message: '服务器内部错误'};return NextResponse.json({ error: response.message },{ status: response.status });
}

最佳实践

1. 错误处理

a) 统一错误处理中心

创建统一的错误处理服务:

// src/lib/errors/ErrorService.js
export class ErrorService {static async handleError(error, type = 'GENERAL') {// 记录错误await this.logError(error, type);// 返回用户友好的错误信息return this.getUserFriendlyError(error, type);}static async logError(error, type) {// 可以集成 Sentry 或其他日志服务console.error(`[${type}] ${new Date().toISOString()}:`, {message: error.message,stack: error.stack,type,});}static getUserFriendlyError(error, type) {const errorMessages = {AUTH: {default: '认证过程中出现错误',invalid_credentials: '用户名或密码错误',account_locked: '账户已被锁定,请联系管理员',},VALIDATION: {default: '输入数据有误',invalid_email: '请输入有效的邮箱地址',weak_password: '密码强度不足',},// ... 其他错误类型};return errorMessages[type]?.[error.code] || errorMessages[type]?.default || '操作失败,请稍后重试';}
}
b) 前端错误展示组件
// src/components/ErrorDisplay.js
export function ErrorDisplay({ error, onRetry }) {return (<div className="rounded-md bg-red-50 p-4"><div className="flex"><div className="flex-shrink-0"><XCircleIcon className="h-5 w-5 text-red-400" /></div><div className="ml-3"><h3 className="text-sm font-medium text-red-800">{error.message}</h3>{onRetry && (<buttononClick={onRetry}className="mt-2 text-sm text-red-600 hover:text-red-500">重试</button>)}</div></div></div>);
}

2. 性能优化

a) 数据库优化

在 Prisma schema 中添加必要的索引:

model User {id            String    @id @default(cuid())email         String    @uniquepassword      String// ... 其他字段@@index([email]) // 为常用查询字段添加索引
}
b) 缓存策略实现
// src/lib/cache/cacheService.js
import { Redis } from 'ioredis';export class CacheService {constructor() {this.redis = new Redis(process.env.REDIS_URL);}async get(key) {const value = await this.redis.get(key);return value ? JSON.parse(value) : null;}async set(key, value, expireSeconds = 3600) {await this.redis.set(key,JSON.stringify(value),'EX',expireSeconds);}async invalidate(key) {await this.redis.del(key);}
}// 使用示例
const cacheService = new CacheService();async function getUserProfile(userId) {const cacheKey = `user:${userId}:profile`;// 尝试从缓存获取const cached = await cacheService.get(cacheKey);if (cached) return cached;// 缓存未命中,从数据库获取const profile = await prisma.user.findUnique({where: { id: userId }});// 存入缓存await cacheService.set(cacheKey, profile);return profile;
}
c) 组件优化
// src/components/LazyLoadedComponent.js
import dynamic from 'next/dynamic';
import { Suspense } from 'react';// 懒加载组件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {loading: () => <div>加载中...</div>,ssr: false // 如果不需要服务端渲染
});// 使用 Suspense 包装
export function OptimizedComponent() {return (<Suspense fallback={<div>加载中...</div>}><HeavyComponent /></Suspense>);
}

3. 用户体验

a) 表单状态管理
// src/hooks/useForm.js
import { useState } from 'react';export function useForm(initialValues = {}) {const [values, setValues] = useState(initialValues);const [errors, setErrors] = useState({});const [isSubmitting, setIsSubmitting] = useState(false);const handleChange = (e) => {const { name, value } = e.target;setValues(prev => ({...prev,[name]: value}));// 清除该字段的错误setErrors(prev => ({...prev,[name]: undefined}));};const validate = (values) => {const errors = {};// 添加验证规则if (!values.email) {errors.email = '请输入邮箱';}if (!values.password) {errors.password = '请输入密码';}return errors;};const handleSubmit = async (onSubmit) => {setIsSubmitting(true);const validationErrors = validate(values);if (Object.keys(validationErrors).length === 0) {try {await onSubmit(values);} catch (error) {setErrors({ submit: error.message });}} else {setErrors(validationErrors);}setIsSubmitting(false);};return {values,errors,isSubmitting,handleChange,handleSubmit};
}
b) 记住登录状态实现
// src/lib/auth/rememberMe.js
export const rememberMeOptions = {// 在 Auth.js 配置中使用session: {strategy: "jwt",maxAge: 30 * 24 * 60 * 60, // 30 天},callbacks: {async jwt({ token, user, account, profile, isNewUser }) {if (user) {token.rememberMe = user.rememberMe;}return token;},async session({ session, token }) {session.rememberMe = token.rememberMe;return session;},},
};// 在登录组件中使用
function LoginForm() {const [rememberMe, setRememberMe] = useState(false);const handleLogin = async (credentials) => {await signIn('credentials', {...credentials,rememberMe,callbackUrl: '/dashboard'});};return (<form>{/* 其他表单字段 */}<div className="flex items-center"><inputtype="checkbox"id="remember-me"checked={rememberMe}onChange={(e) => setRememberMe(e.target.checked)}/><label htmlFor="remember-me" className="ml-2">记住我</label></div></form>);
}

扩展功能

1. OAuth 社交登录

a) 配置社交登录提供商
// src/app/api/auth/[...nextauth]/route.js
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";export const authOptions = {providers: [GoogleProvider({clientId: process.env.GOOGLE_ID,clientSecret: process.env.GOOGLE_SECRET,}),GithubProvider({clientId: process.env.GITHUB_ID,clientSecret: process.env.GITHUB_SECRET,}),// ... 其他提供商],callbacks: {async signIn({ user, account, profile }) {// 处理首次社交登录if (account.provider === "google" || account.provider === "github") {const existingUser = await prisma.user.findUnique({where: { email: user.email }});if (!existingUser) {// 创建新用户await prisma.user.create({data: {email: user.email,name: user.name,image: user.image,// 设置社交登录特定字段provider: account.provider,providerId: account.providerAccountId,}});}}return true;}}
};
b) 社交登录按钮组件
// src/components/SocialLogin.js
export function SocialLoginButtons() {return (<div className="space-y-3"><buttononClick={() => signIn('google')}className="w-full flex items-center justify-center gap-2 bg-white border border-gray-300 rounded-lg px-4 py-2"><GoogleIcon />使用 Google 登录</button><buttononClick={() => signIn('github')}className="w-full flex items-center justify-center gap-2 bg-gray-800 text-white rounded-lg px-4 py-2"><GithubIcon />使用 GitHub 登录</button></div>);
}

2. 双因素认证(2FA)

a) 2FA 配置
// src/lib/auth/twoFactor.js
import { authenticator } from 'otplib';
import QRCode from 'qrcode';export class TwoFactorService {// 生成密钥static generateSecret() {return authenticator.generateSecret();}// 生成 QR 码 URLstatic async generateQRCode(email, secret) {const otpauth = authenticator.keyuri(email,'YourApp',secret);return await QRCode.toDataURL(otpauth);}// 验证码验证static verifyToken(token, secret) {return authenticator.verify({token,secret});}
}
b) 2FA 设置页面
// src/app/settings/2fa/page.js
'use client';export default function TwoFactorSetupPage() {const [secret, setSecret] = useState('');const [qrCode, setQrCode] = useState('');async function setupTwoFactor() {const response = await fetch('/api/auth/2fa/setup', {method: 'POST'});const { secret, qrCode } = await response.json();setSecret(secret);setQrCode(qrCode);}return (<div><h2>设置双因素认证</h2><button onClick={setupTwoFactor}>开始设置</button>{qrCode && (<div><img src={qrCode} alt="2FA QR Code" /><p>请使用认证器 App 扫描此二维码</p></div>)}</div>);
}

3. 权限管理

a) 角色定义
// src/types/auth.ts
export enum UserRole {USER = 'user',ADMIN = 'admin',EDITOR = 'editor'
}export interface Permission {action: string;subject: string;
}export const rolePermissions: Record<UserRole, Permission[]> = {[UserRole.USER]: [{ action: 'read', subject: 'posts' },{ action: 'create', subject: 'comments' }],[UserRole.EDITOR]: [{ action: 'read', subject: 'posts' },{ action: 'create', subject: 'posts' },{ action: 'update', subject: 'posts' }],[UserRole.ADMIN]: [{ action: 'manage', subject: 'all' }]
};
b) 权限检查中间件
// src/middleware/checkPermission.ts
import { createMiddlewareDecorator } from '@mantine/next';
import { UserRole, rolePermissions } from '@/types/auth';export const checkPermission = createMiddlewareDecorator(async (req, res, next) => {const session = await getServerSession(req);const userRole = session?.user?.role as UserRole;if (!userRole) {return res.status(403).json({ error: '未授权访问' });}const permissions = rolePermissions[userRole];const requiredPermission = {action: req.method.toLowerCase(),subject: req.url.split('/').pop()};const hasPermission = permissions.some(p => (p.action === 'manage' && p.subject === 'all') ||(p.action === requiredPermission.action &&p.subject === requiredPermission.subject));if (!hasPermission) {return res.status(403).json({ error: '权限不足' });}return next();}
);

4. 密码重置

a) 重置密码流程
// src/app/api/auth/reset-password/route.js
export async function POST(request) {const { email } = await request.json();// 生成重置令牌const resetToken = crypto.randomBytes(32).toString('hex');const resetTokenExpiry = new Date(Date.now() + 3600000); // 1小时后过期// 更新用户记录await prisma.user.update({where: { email },data: {resetToken,resetTokenExpiry}});// 发送重置邮件await sendResetEmail(email, resetToken);return NextResponse.json({ message: '重置链接已发送到邮箱' });
}
b) 重置密码邮件服务
// src/lib/email/resetPassword.js
import { createTransport } from 'nodemailer';export async function sendResetEmail(email, token) {const transporter = createTransport({host: process.env.SMTP_HOST,port: process.env.SMTP_PORT,auth: {user: process.env.SMTP_USER,pass: process.env.SMTP_PASS}});const resetUrl = `${process.env.NEXT_PUBLIC_URL}/reset-password?token=${token}`;await transporter.sendMail({from: '"YourApp" <noreply@yourapp.com>',to: email,subject: '密码重置请求',html: `<h1>密码重置</h1><p>点击下面的链接重置密码:</p><a href="${resetUrl}">${resetUrl}</a><p>此链接1小时内有效</p>`});
}
c) 重置密码页面
// src/app/reset-password/page.js
'use client';export default function ResetPasswordPage() {const [password, setPassword] = useState('');const [confirmPassword, setConfirmPassword] = useState('');const searchParams = useSearchParams();const token = searchParams.get('token');async function handleSubmit(e) {e.preventDefault();if (password !== confirmPassword) {return setError('密码不匹配');}const response = await fetch('/api/auth/reset-password/confirm', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ token, password })});if (response.ok) {router.push('/login?reset=success');} else {setError('重置密码失败');}}return (<form onSubmit={handleSubmit}><inputtype="password"value={password}onChange={(e) => setPassword(e.target.value)}placeholder="新密码"/><inputtype="password"value={confirmPassword}onChange={(e) => setConfirmPassword(e.target.value)}placeholder="确认密码"/><button type="submit">重置密码</button></form>);
}

总结

Next.js + Prisma + Auth.js 的组合提供了一个强大且灵活的认证方案。关键是:

  • • 正确配置 Auth.js
  • • 实现必要的 API
  • • 做好安全防护
  • • 注意用户体验

掌握这个方案后,可以快速在项目中实现可靠的用户认证系统。

环境配置

创建 .env 文件:

DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"

确保将 .env 添加到 .gitignore

.env
.env.local
node_modules

实际应用示例

1. 受保护的仪表板页面

// src/app/dashboard/page.js
import { getServerSession } from "next-auth/next";
import { redirect } from "next/navigation";export default async function DashboardPage() {const session = await getServerSession();if (!session) {redirect("/login");}return (<div><h1>欢迎, {session.user.name}</h1>{/* 仪表板内容 */}</div>);
}

2. 用户状态管理示例

// src/components/UserMenu.js
'use client';
import { useSession } from "next-auth/react";export default function UserMenu() {const { data: session, status } = useSession();if (status === "loading") {return <div>加载中...</div>;}return session ? (<div><span>欢迎, {session.user.name}</span><button onClick={() => signOut()}>退出</button></div>) : (<button onClick={() => signIn()}>登录</button>);
}

http://www.mrgr.cn/news/58828.html

相关文章:

  • C语言基础课程 - 第二天
  • 7、整数反转-cangjie
  • 【SQL实验】表的更新和简单查询
  • Spring Boot技术中小企业设备管理系统设计与实践
  • C#从零开始学习(用户界面)(unity Lab4)
  • Nginx 配置后网站图片加载出来一半或者不出来解决方案
  • 一篇文章告诉你什么是BloomFilter
  • 【网络安全初识】——互联网发展史
  • 数据治理与主数据管理:现代企业数据管理的核心
  • 【软件工程】软件工程入门
  • 整合Mybatis-plus及最佳实践
  • 聊聊Web3D 发展趋势
  • app头部氛围该如何设计,这里有50个示例
  • GLM-4-Voice:智谱AI的端到端中英语音对话模型
  • 基于SSM农业信息管理系统的设计l
  • unity开发之绳子制作 obi rope
  • 系统架构设计师教程 第2章 2.3 计算机软件 笔记
  • 【动态规划】回文串问题
  • Python 语法与数据类型详解
  • 使用 Pygame 创建生命游戏(Conway‘s Game of Life)
  • NumPy学习第六课(1):数组的高级索引
  • 【JAVA毕业设计】基于Vue和SpringBoot的房产销售系统
  • 业务开发如何才能独立于框架
  • XSS攻击原理与解决方法
  • STM32基于LL库的USART+DMA使用
  • 数据可视化技术综述(5)数据的存储