身份认证
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 提供商
- 在提供商的开发者控制台创建 OAuth 应用
- 将凭据添加到
.env.local - 将提供商添加到
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");
},
},
},
},
});