Docs/modules/身份认证

身份认证

NextShip 使用 Better Auth 进行身份认证,使用 Casbin 进行基于角色的访问控制。

功能特性

  • 邮箱/密码认证
  • OAuth 提供商(Google、GitHub)
  • 魔法链接登录
  • 双因素认证(2FA)
  • 会话管理
  • 基于角色的访问控制(RBAC)

配置

认证配置位于 src/lib/auth.ts

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./db";
 
export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

环境变量

# 必需
BETTER_AUTH_SECRET=your-random-secret
 
# OAuth 提供商(可选)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

获取当前用户

服务端组件

import { auth } from "@/lib/auth";
import { headers } from "next/headers";
 
export default async function Page() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });
 
  if (!session) {
    redirect("/login");
  }
 
  return <div>欢迎,{session.user.name}</div>;
}

客户端组件

"use client";
 
import { authClient } from "@/lib/auth-client";
 
export function UserProfile() {
  const { data: session } = authClient.useSession();
 
  if (!session) return null;
 
  return <div>{session.user.email}</div>;
}

受保护的路由

使用布局保护

// src/app/[locale]/(dashboard)/layout.tsx
import { requireAuth } from "@/lib/permissions";
 
export default async function DashboardLayout({ children }) {
  await requireAuth(); // 未认证时重定向到登录页
 
  return <DashboardShell>{children}</DashboardShell>;
}

使用权限辅助函数

// src/lib/permissions.ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "@/i18n/navigation";
 
export async function requireAuth() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });
 
  if (!session) {
    redirect("/login");
  }
 
  return session;
}
 
export async function requireAdmin() {
  const session = await requireAuth();
 
  const isAdmin = await checkPermission(session.user.id, "admin", "access");
  if (!isAdmin) {
    throw new Error("需要管理员权限");
  }
 
  return session;
}
 
export async function requirePermission(object: string, action: string) {
  const session = await requireAuth();
 
  const hasPermission = await checkPermission(session.user.id, object, action);
  if (!hasPermission) {
    throw new Error("权限不足");
  }
 
  return session;
}

基于角色的访问控制(RBAC)

NextShip 使用 Casbin 实现灵活的 RBAC。详情请参阅 RBAC 文档

角色层级

superadmin
    └── admin
          └── user
  • superadmin: 完全访问所有功能
  • admin: 可访问管理员仪表板、用户管理
  • user: 基本访问自己的资源

检查角色

// 在 Server Actions 或 API 路由中
import { getUserRoles, checkUserRole } from "@/server/actions/permission";
 
// 获取用户的所有角色
const roles = await getUserRoles(userId);
// 返回: ["user", "admin"]
 
// 检查用户是否具有特定角色
const isAdmin = await checkUserRole("admin");
// 返回: true/false

保护管理员页面

// src/app/[locale]/(dashboard)/users/page.tsx
import { requireAdmin } from "@/lib/permissions";
 
export default async function UsersPage() {
  await requireAdmin(); // 只有管理员可以访问
 
  const users = await getUsers();
  return <UsersTable users={users} />;
}

数据库架构

Better Auth 使用以下表(定义在 src/lib/db/schema.ts):

  • users - 用户账户
  • sessions - 活跃会话
  • accounts - OAuth 提供商连接
  • verifications - 邮箱验证令牌

添加 OAuth 提供商

  1. 在提供商的开发者控制台创建 OAuth 应用
  2. 将凭据添加到 .env.local
  3. 将提供商添加到 src/lib/auth.ts

查看 Better Auth 文档 了解所有可用的提供商。

自动角色分配

新用户通过 Better Auth 数据库钩子自动获得 "user" 角色:

// src/lib/auth.ts
export const auth = betterAuth({
  // ...
  databaseHooks: {
    user: {
      create: {
        after: async (user) => {
          // 分配默认的 "user" 角色
          await assignUserRole(user.id, "user");
        },
      },
    },
  },
});