Next-Auth 认证系统:用户与管理员双角色登录配置

概述

本文档介绍了如何使用 Next-Auth 配置一个同时支持普通用户和管理员用户登录的认证系统。

基本配置

首先,我们需要设置 Next-Auth 的基本配置,包括提供者、回调函数和页面路由。

typescript 复制代码
import type { NextAuthConfig } from 'next-auth'
import type { User } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { LoginSchema } from '@/types/auth'
import { prisma } from '@/lib/prisma'
import bcrypt from 'bcryptjs'

export const authOptions = {
  providers: [
    // 配置提供者
  ],
  callbacks: {
    // 配置回调函数
  },
  pages: {
    // 配置页面路由
  },
} satisfies NextAuthConfig

配置认证提供者

需要配置两个 Credentials 提供者,分别用于普通用户和管理员用户的登录。

typescript 复制代码
providers: [
  Credentials({
    id: 'user-login', // 普通用户登录
    name: 'User Credentials',
    async authorize(credentials) {
      try {
        const validatedFields = LoginSchema.safeParse(credentials)
        if (!validatedFields.success) return null

        const { email, password } = validatedFields.data
        const user = await prisma.user.findUnique({
          where: { email },
          select: {
            id: true,
            email: true,
            password: true,
            username: true,
            avatar_url: true,
          },
        })

        if (!user?.password) return null

        const passwordsMatch = await bcrypt.compare(password, user.password)
        if (!passwordsMatch) return null

        return {
          id: user.id,
          email: user.email,
          username: user.username || user.email?.split('@')[0] || '',
          avatar_url: user.avatar_url || '',
          role: 'user', // 添加角色标识
        }
      } catch (error) {
        console.error('User authorization error:', error)
        return null
      }
    },
  }),
  Credentials({
    id: 'admin-login', // 管理员登录
    name: 'Admin Credentials',
    async authorize(credentials) {
      try {
        const validatedFields = LoginSchema.safeParse(credentials)
        if (!validatedFields.success) return null

        const { email, password } = validatedFields.data
        const admin = await prisma.adminUser.findUnique({
          where: { email },
          select: {
            id: true,
            email: true,
            password: true,
            username: true,
            avatar_url: true,
            role: true,
          },
        })

        if (!admin?.password) return null

        const passwordsMatch = await bcrypt.compare(password, admin.password)
        if (!passwordsMatch) return null

        return {
          id: admin.id,
          email: admin.email,
          username: admin.username,
          avatar_url: admin.avatar_url || '',
          role: 'admin', // 添加角色标识
        }
      } catch (error) {
        console.error('Admin authorization error:', error)
        return null
      }
    },
  }),
],

配置回调函数

回调函数用于处理会话和 JWT 令牌的自定义逻辑。

typescript 复制代码
callbacks: {
  async session({ session, token }) {
    if (session.user) {
      session.user.username = token.username as string
      session.user.id = token.sub as string
      session.user.avatar_url = token.avatar_url as string
      session.user.role = token.role as string // 添加角色
    }
    return session
  },
  async jwt({ token, user }) {
    if (user) {
      token.username = user.username
      token.sub = user.id
      token.avatar_url = user.avatar_url
      token.role = user.role // 添加角色
    }
    return token
  },
},

配置页面路由

配置自定义的登录页面和错误页面。

typescript 复制代码
pages: {
  signIn: '/login', // 普通用户登录页
  error: '/auth/error', // 错误页面
},

扩展 Session 类型

为了在 TypeScript 中正确识别自定义的会话属性,我们需要扩展 Next-Auth 的类型定义。

typescript 复制代码
// types/next-auth.d.ts
import { DefaultSession } from "next-auth"

declare module "next-auth" {
  interface Session {
    user: {
      id: string
      username: string
      avatar_url: string
      role: string
    } & DefaultSession["user"]
  }

  interface User {
    id: string
    username: string
    avatar_url: string
    role: string
  }
}

区分用户和管理员

如果需要在会话中明确区分用户和管理员,可以修改 session 回调函数:

typescript 复制代码
async session({ session, token }) {
  if (session.user) {
    session.user.username = token.username as string
    session.user.id = token.sub as string
    session.user.avatar_url = token.avatar_url as string
    session.user.role = token.role as string
  }
  
  // 添加区分的用户和管理员对象
  if (token.role === 'admin') {
    session.admin = {
      id: token.sub as string,
      email: session.user?.email,
      username: token.username as string,
      avatar_url: token.avatar_url as string,
      role: 'admin'
    }
  } else {
    session.admin = null // 如果不是管理员,设置为 null
  }
  
  return session
},

同时更新类型定义:

typescript 复制代码
// types/next-auth.d.ts
interface Session {
  user: {
    id: string
    username: string
    avatar_url: string
    role: string
  } & DefaultSession["user"]
  admin: {
    id: string
    email?: string | null
    username: string
    avatar_url: string
    role: string
  } | null
}

使用方式

在组件中使用会话信息:

typescript 复制代码
import { useSession } from "next-auth/react"

function MyComponent() {
  const { data: session } = useSession()
  
  // 检查是否是管理员
  if (session?.user?.role === 'admin') {
    // 管理员逻辑
  } else {
    // 普通用户逻辑
  }
  
  // 或者使用扩展的 admin 对象
  if (session?.admin) {
    // 管理员逻辑
  } else {
    // 普通用户逻辑
  }
}

登录方式

在登录表单中,可以通过指定 providerId 来选择使用哪个认证提供者:

typescript 复制代码
import { signIn } from "next-auth/react"

// 普通用户登录
signIn("user-login", { 
  email, 
  password,
  redirect: true,
  callbackUrl: "/dashboard"
})

// 管理员登录
signIn("admin-login", { 
  email, 
  password,
  redirect: true,
  callbackUrl: "/admin/dashboard"
})
相关推荐
weixin_493503674 小时前
Vue3 前端生成 PDF:会员证书与活动签到表的三种打印方案与踩坑记录
前端·pdf·状态模式
傻啦嘿哟4 小时前
某招聘平台爬虫:爬取招聘岗位数据,分析各城市薪资水平
开发语言·爬虫·python
2501_933670794 小时前
2026秋招量化分析岗技能栈:Python、SQL、统计建模、回测项目怎么准备
开发语言·python·sql
李少兄5 小时前
JavaScript 数据类型完全指南
开发语言·javascript·ecmascript
Seoyoneh6 小时前
Agentic Workflow编排架构:云客服从“被动响应”迈向“主动执行”的技术实现
java·开发语言·架构
Tangyuewei6 小时前
Java 写 Agent:模型只是组件
java·开发语言
小玮看世界7 小时前
[Python]从合并区间到传感器融合区:合并区间在传感器区域融合的实际落地
开发语言·python
尾善爱看海7 小时前
前端算法与手写题集
前端·算法
徐小夕7 小时前
JitWord 4.0 万字分享:从协同工具到AI Word操作系统,聊聊3年产品创业史
前端·vue.js·后端
繁星蓝雨7 小时前
C++的设计与演进大纲(如何从C语言一步步成长为C++)
c语言·开发语言·c++·c++历史·c++演进