Skip to content

Next.js 身份验证 🔐

🎯 概述

身份验证是保护应用安全的关键。Next.js 支持多种认证方案,包括 NextAuth.js、JWT、Session 等。

📦 NextAuth.js (推荐)

安装

bash
npm install next-auth

基本配置

tsx
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
import CredentialsProvider from 'next-auth/providers/credentials'

const handler = NextAuth({
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: "邮箱", type: "email" },
        password: { label: "密码", type: "password" }
      },
      async authorize(credentials) {
        const user = await verifyUser(credentials)
        if (user) {
          return user
        }
        return null
      }
    })
  ],
  pages: {
    signIn: '/login',
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
      }
      return token
    },
    async session({ session, token }) {
      session.user.id = token.id
      return session
    }
  }
})

export { handler as GET, handler as POST }

使用认证

tsx
// app/profile/page.tsx
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'

export default async function Profile() {
  const session = await getServerSession()
  
  if (!session) {
    redirect('/login')
  }
  
  return <div>欢迎,{session.user.name}</div>
}

// components/LoginButton.tsx
'use client'

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

export default function LoginButton() {
  const { data: session } = useSession()
  
  if (session) {
    return (
      <button onClick={() => signOut()}>
        退出
      </button>
    )
  }
  
  return (
    <button onClick={() => signIn()}>
      登录
    </button>
  )
}

🚀 JWT 认证

创建 JWT 工具

tsx
// lib/jwt.ts
import jwt from 'jsonwebtoken'

const SECRET = process.env.JWT_SECRET!

export function signToken(payload: any) {
  return jwt.sign(payload, SECRET, { expiresIn: '7d' })
}

export function verifyToken(token: string) {
  try {
    return jwt.verify(token, SECRET)
  } catch {
    return null
  }
}

登录 API

tsx
// app/api/login/route.ts
import { NextResponse } from 'next/server'
import { signToken } from '@/lib/jwt'

export async function POST(request: Request) {
  const { email, password } = await request.json()
  
  const user = await verifyCredentials(email, password)
  
  if (!user) {
    return NextResponse.json(
      { error: '凭据无效' },
      { status: 401 }
    )
  }
  
  const token = signToken({ userId: user.id })
  
  return NextResponse.json({ token, user })
}

中间件保护

tsx
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { verifyToken } from '@/lib/jwt'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value
  
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  
  const payload = verifyToken(token)
  
  if (!payload) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  
  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*']
}

📝 Session 认证

使用 Cookies

tsx
// app/api/login/route.ts
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const { email, password } = await request.json()
  
  const user = await verifyCredentials(email, password)
  
  if (!user) {
    return NextResponse.json({ error: '登录失败' }, { status: 401 })
  }
  
  const sessionId = await createSession(user.id)
  
  cookies().set('session', sessionId, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7 // 7 days
  })
  
  return NextResponse.json({ user })
}

获取当前用户

tsx
// lib/auth.ts
import { cookies } from 'next/headers'

export async function getCurrentUser() {
  const sessionId = cookies().get('session')?.value
  
  if (!sessionId) {
    return null
  }
  
  const session = await getSession(sessionId)
  
  if (!session) {
    return null
  }
  
  return session.user
}

📚 最佳实践

1. 保护 API 路由

tsx
// lib/auth.ts
export async function requireAuth(request: Request) {
  const token = request.headers.get('Authorization')?.split(' ')[1]
  
  if (!token) {
    throw new Error('未授权')
  }
  
  const payload = verifyToken(token)
  
  if (!payload) {
    throw new Error('令牌无效')
  }
  
  return payload
}

// app/api/posts/route.ts
import { requireAuth } from '@/lib/auth'

export async function POST(request: Request) {
  try {
    const user = await requireAuth(request)
    // 创建文章
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 401 })
  }
}

2. 密码加密

bash
npm install bcrypt
tsx
import bcrypt from 'bcrypt'

// 注册时加密密码
const hashedPassword = await bcrypt.hash(password, 10)

// 登录时验证密码
const isValid = await bcrypt.compare(password, user.hashedPassword)

3. 刷新令牌

tsx
// app/api/refresh/route.ts
export async function POST(request: Request) {
  const { refreshToken } = await request.json()
  
  const payload = verifyToken(refreshToken)
  
  if (!payload) {
    return NextResponse.json({ error: '令牌无效' }, { status: 401 })
  }
  
  const newToken = signToken({ userId: payload.userId })
  
  return NextResponse.json({ token: newToken })
}

🔗 相关资源


下一步:学习 Next.js 数据库集成