xiaso-flnxi-website/AUTH_INTEGRATION_GUIDE.md
Your Name b1eab32c75 初始提交: FLNXI电商素材系统官方网站
- Next.js 项目框架
- 完整的认证系统集成
- 数据库配置和部署脚本
- 产品价格体系和文档
- Docker 部署配置
2025-12-13 21:38:32 +08:00

14 KiB
Raw Blame History

NextAuth.js (Auth.js) 集成指南

📋 方案介绍

NextAuth.js 是最流行的 Next.js 认证解决方案,完全开源免费。

核心优势

  • ✅ 5分钟集成 - 超级简单
  • ✅ 完全免费 - 无用户数限制
  • ✅ 多种登录方式 - 邮箱、Google、GitHub、微信等
  • ✅ 安全可靠 - OAuth 2.0 标准
  • ✅ 完美集成 - 专为 Next.js 设计

🚀 快速集成步骤

第一步:安装依赖

cd /Users/lanmu/Desktop/xiaso
npm install next-auth@latest

第二步:配置环境变量

创建 .env.local 文件:

# NextAuth 密钥(必须)
NEXTAUTH_SECRET=your-secret-key-here-replace-with-random-string
NEXTAUTH_URL=https://scs.flnxi.com

# Google OAuth(可选)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# GitHub OAuth(可选)
GITHUB_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret

生成密钥:

openssl rand -base64 32

第三步:创建 Auth API 路由

创建文件:app/api/auth/[...nextauth]/route.ts

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import GitHubProvider from "next-auth/providers/github"
import CredentialsProvider from "next-auth/providers/credentials"

const handler = NextAuth({
  providers: [
    // 邮箱密码登录
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: "邮箱", type: "email", placeholder: "user@example.com" },
        password: { label: "密码", type: "password" }
      },
      async authorize(credentials, req) {
        // 这里添加您的用户验证逻辑
        // 例如:从数据库查询用户
        const user = { id: "1", name: "用户", email: credentials?.email }
        
        if (user) {
          return user
        }
        return null
      }
    }),
    
    // Google 登录(需要配置 OAuth)
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    
    // GitHub 登录(需要配置 OAuth)
    GitHubProvider({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
  ],
  
  // 自定义登录页面
  pages: {
    signIn: '/auth/signin',
    // signOut: '/auth/signout',
    // error: '/auth/error',
  },
  
  // 回调函数
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
      }
      return token
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string
      }
      return session
    },
  },
  
  // 会话策略
  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 天
  },
})

export { handler as GET, handler as POST }

第四步:创建登录页面

创建文件:app/auth/signin/page.tsx

'use client'

import { signIn } from 'next-auth/react'
import { useState } from 'react'
import { Mail, Lock, Chrome, Github } from 'lucide-react'

export default function SignInPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    setLoading(true)
    
    await signIn('credentials', {
      email,
      password,
      callbackUrl: '/',
    })
    
    setLoading(false)
  }

  return (
    <div className="min-h-screen bg-gradient-to-b from-apple-gray-50 to-white flex items-center justify-center px-4">
      <div className="max-w-md w-full">
        {/* Logo */}
        <div className="text-center mb-8">
          <h1 className="text-3xl font-bold text-apple-gray-900 mb-2">
            登录 FLNXI
          </h1>
          <p className="text-apple-gray-600">
            欢迎回来,请登录您的账户
          </p>
        </div>

        {/* 登录卡片 */}
        <div className="bg-white rounded-2xl apple-shadow-lg p-8">
          {/* 邮箱密码登录 */}
          <form onSubmit={handleSubmit} className="space-y-4 mb-6">
            <div>
              <label className="block text-sm font-medium text-apple-gray-700 mb-2">
                邮箱地址
              </label>
              <div className="relative">
                <Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-apple-gray-400" />
                <input
                  type="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  className="w-full pl-10 pr-4 py-3 border border-apple-gray-200 rounded-xl focus:ring-2 focus:ring-apple-blue focus:border-transparent outline-none"
                  placeholder="your@email.com"
                  required
                />
              </div>
            </div>

            <div>
              <label className="block text-sm font-medium text-apple-gray-700 mb-2">
                密码
              </label>
              <div className="relative">
                <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-apple-gray-400" />
                <input
                  type="password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                  className="w-full pl-10 pr-4 py-3 border border-apple-gray-200 rounded-xl focus:ring-2 focus:ring-apple-blue focus:border-transparent outline-none"
                  placeholder="••••••••"
                  required
                />
              </div>
            </div>

            <button
              type="submit"
              disabled={loading}
              className="w-full py-3 bg-gradient-to-r from-apple-blue to-blue-600 text-white font-semibold rounded-xl button-hover apple-shadow disabled:opacity-50"
            >
              {loading ? '登录中...' : '登录'}
            </button>
          </form>

          {/* 分割线 */}
          <div className="relative my-6">
            <div className="absolute inset-0 flex items-center">
              <div className="w-full border-t border-apple-gray-200" />
            </div>
            <div className="relative flex justify-center text-sm">
              <span className="px-4 bg-white text-apple-gray-500">
                或使用第三方登录
              </span>
            </div>
          </div>

          {/* 第三方登录 */}
          <div className="space-y-3">
            <button
              onClick={() => signIn('google', { callbackUrl: '/' })}
              className="w-full flex items-center justify-center space-x-3 py-3 border-2 border-apple-gray-200 rounded-xl hover:bg-apple-gray-50 transition-all font-medium"
            >
              <Chrome className="w-5 h-5 text-red-500" />
              <span>使用 Google 登录</span>
            </button>

            <button
              onClick={() => signIn('github', { callbackUrl: '/' })}
              className="w-full flex items-center justify-center space-x-3 py-3 border-2 border-apple-gray-200 rounded-xl hover:bg-apple-gray-50 transition-all font-medium"
            >
              <Github className="w-5 h-5" />
              <span>使用 GitHub 登录</span>
            </button>
          </div>

          {/* 底部链接 */}
          <div className="mt-6 text-center text-sm text-apple-gray-600">
            还没有账户?
            <a href="/auth/signup" className="text-apple-blue hover:underline ml-1">
              立即注册
            </a>
          </div>
        </div>

        {/* 返回首页 */}
        <div className="text-center mt-6">
          <a href="/" className="text-apple-gray-600 hover:text-apple-blue transition-colors">
            ← 返回首页
          </a>
        </div>
      </div>
    </div>
  )
}

第五步:添加 Session Provider

修改 app/layout.tsx:

import { getServerSession } from "next-auth/next"
import SessionProvider from '@/components/SessionProvider'

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const session = await getServerSession()

  return (
    <html lang="zh-CN">
      <body>
        <SessionProvider session={session}>
          {children}
        </SessionProvider>
      </body>
    </html>
  )
}

创建 components/SessionProvider.tsx:

'use client'

import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react'

export default function SessionProvider({
  children,
  session,
}: {
  children: React.ReactNode
  session: any
}) {
  return (
    <NextAuthSessionProvider session={session}>
      {children}
    </NextAuthSessionProvider>
  )
}

第六步:在 Header 中添加登录按钮

修改 components/Header.tsx,添加登录状态:

'use client'

import { useSession, signIn, signOut } from 'next-auth/react'
import { User, LogOut } from 'lucide-react'

export default function Header({ scrolled }: HeaderProps) {
  const { data: session, status } = useSession()

  return (
    <header>
      {/* ... 其他代码 ... */}
      
      {/* 添加登录/用户信息 */}
      <div className="flex items-center space-x-4">
        {status === 'loading' ? (
          <div className="w-8 h-8 rounded-full bg-apple-gray-200 animate-pulse" />
        ) : session ? (
          // 已登录
          <div className="flex items-center space-x-3">
            <span className="text-sm text-apple-gray-700">
              {session.user?.name || session.user?.email}
            </span>
            <button
              onClick={() => signOut()}
              className="flex items-center space-x-2 px-4 py-2 text-apple-gray-700 hover:text-apple-blue transition-colors"
            >
              <LogOut className="w-4 h-4" />
              <span>退出</span>
            </button>
          </div>
        ) : (
          // 未登录
          <button
            onClick={() => signIn()}
            className="flex items-center space-x-2 px-5 py-2 bg-apple-blue text-white rounded-full text-sm font-medium button-hover apple-shadow"
          >
            <User className="w-4 h-4" />
            <span>登录</span>
          </button>
        )}
      </div>
    </header>
  )
}

🎨 高级功能

1. 添加注册页面

创建 app/auth/signup/page.tsx(类似登录页面)

2. 保护页面(需要登录才能访问)

// app/dashboard/page.tsx
import { getServerSession } from "next-auth/next"
import { redirect } from "next/navigation"

export default async function DashboardPage() {
  const session = await getServerSession()
  
  if (!session) {
    redirect('/auth/signin')
  }

  return <div>受保护的页面内容</div>
}

3. 添加微信登录

npm install next-auth-wechat
// 在 [...nextauth]/route.ts 中添加
import WeChatProvider from "next-auth-wechat"

providers: [
  WeChatProvider({
    clientId: process.env.WECHAT_APP_ID!,
    clientSecret: process.env.WECHAT_APP_SECRET!,
  }),
]

📦 部署配置

1. 更新环境变量

在服务器上设置环境变量:

ssh root@45.249.208.118

# 编辑环境变量
cd /opt/flnxi-website
nano .env.local

添加:

NEXTAUTH_SECRET=<生成的密钥>
NEXTAUTH_URL=https://scs.flnxi.com

2. 更新 Docker 配置

修改 docker-compose.yml:

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "4782:3000"
    environment:
      - NODE_ENV=production
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - NEXTAUTH_URL=${NEXTAUTH_URL}
    env_file:
      - .env.local
    restart: unless-stopped

3. 重新构建部署

docker-compose down
docker-compose up -d --build

🔐 配置第三方登录

Google OAuth 配置

  1. 访问 Google Cloud Console
  2. 创建新项目
  3. 启用 Google+ API
  4. 创建 OAuth 2.0 凭据
  5. 添加授权重定向 URI:https://scs.flnxi.com/api/auth/callback/google

GitHub OAuth 配置

  1. 访问 GitHub Settings > Developer settings
  2. 创建 New OAuth App
  3. 添加授权回调 URL:https://scs.flnxi.com/api/auth/callback/github

📊 功能对比

功能 NextAuth.js Clerk Logto
开源免费 ✅ ⚠️ 有限制 ✅
集成难度 简单 超简单 中等
用户数限制 无限制 10k MAU 无限制
UI 自定义 完全自定义 有限 可自定义
中文支持 需自己实现 支持 原生支持
需要额外服务 ❌ ❌ ✅

✅ 集成检查清单

  • 安装 next-auth 依赖
  • 创建 API 路由 [...nextauth]/route.ts
  • 配置环境变量(NEXTAUTH_SECRET)
  • 创建登录页面
  • 添加 SessionProvider
  • 更新 Header 组件
  • 测试邮箱登录
  • (可选)配置 Google OAuth
  • (可选)配置 GitHub OAuth
  • 部署到服务器
  • 测试生产环境

🎯 推荐时间线

  • 5分钟:基础集成(邮箱登录)
  • +10分钟:美化登录页面
  • +15分钟:添加 Google/GitHub 登录
  • +10分钟:测试和部署

总计:40分钟完成完整集成


📞 获取帮助

如果遇到问题:

  1. 查看 NextAuth.js 官方文档
  2. 搜索 GitHub Issues
  3. 加入 Discord 社区

准备好了吗?让我开始帮您集成! 🚀