Next.js 教程系列(十四)NextAuth.js 身份认证与授权

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

第十四章:NextAuth.js 身份认证与授权

理论讲解

1. 认证与授权的区别

  • 认证(Authentication):确认用户身份,常见方式有账号密码、OAuth、短信验证码等。
  • 授权(Authorization):确认用户是否有权限访问某资源或操作某功能,如管理员权限、资源所有权等。
  • 在企业级应用中,认证和授权往往结合使用,确保系统安全。

2. NextAuth.js 简介与核心原理

NextAuth.js 是 Next.js 生态中最流行的认证解决方案,支持多种认证方式(OAuth、Credentials、Email 等),可无缝集成到 API Routes 和 App Router,支持服务端渲染、静态生成、API 保护等多种场景。

核心特性:

  • 支持多 Provider(GitHub、Google、微信、企业微信等)
  • 支持基于凭证的自定义登录
  • Session 管理灵活,支持 JWT 和数据库存储
  • 可扩展的回调机制,便于权限控制、用户信息增强
  • 与 Next.js SSR/ISR/Server Actions 深度集成
  • 丰富的安全机制(CSRF、加密、Token 刷新等)

3. NextAuth.js 的工作流程

  1. 用户在前端页面发起登录请求(如点击 GitHub 登录按钮)
  2. 跳转到第三方 Provider 认证页面,用户授权后回调到 Next.js 应用
  3. NextAuth.js 通过 API Route /api/auth/* 处理回调,生成 Session
  4. Session 可通过前端 hook、服务端 API、Server Actions 获取
  5. 退出登录时销毁 Session,前端自动跳转到登录页

4. Session 管理与安全机制

  • 支持 JWT(无状态)和数据库(有状态)两种 Session 存储方式
  • 内置 CSRF 防护、Session 加密、Token 过期与刷新机制
  • 可自定义 Session 生命周期、加密算法、回调逻辑
  • 支持多端同步登录状态,适配移动端、桌面端等多场景

5. 与 Next.js 各种渲染模式协作

  • SSR/SSG :可在 getServerSidePropsgetStaticProps、API Routes、Server Actions 中安全获取 Session
  • App Router:可在 Server Component、Server Action、API Route 中获取 Session,实现页面和接口保护
  • API 保护:可在 API Route、Server Action 内部校验 Session,实现接口级权限控制

详细代码示例

1. NextAuth.js 基础配置(支持 GitHub OAuth 和自定义凭证登录)

ts 复制代码
// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import GitHubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';

export default NextAuth({
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        username: { label: '用户名', type: 'text' },
        password: { label: '密码', type: 'password' },
      },
      async authorize(credentials) {
        // 这里可接入数据库校验
        if (credentials?.username === 'admin' && credentials?.password === '123456') {
          return { id: 1, name: '管理员', email: 'admin@example.com' };
        }
        return null;
      },
    }),
  ],
  session: {
    strategy: 'jwt', // 也可用 'database'
    maxAge: 60 * 60 * 24, // 1天
  },
  callbacks: {
    async session({ session, token }) {
      // 可扩展 session 信息
      session.userId = token.sub;
      return session;
    },
    async jwt({ token, user }) {
      if (user) token.sub = user.id;
      return token;
    },
  },
  pages: {
    signIn: '/auth/signin',
    error: '/auth/error',
  },
  secret: process.env.NEXTAUTH_SECRET,
});

2. 登录页面与多 Provider 支持

tsx 复制代码
// pages/auth/signin.tsx
import { getProviders, signIn } from 'next-auth/react';
import { useEffect, useState } from 'react';

export default function SignIn() {
  const [providers, setProviders] = useState<any>({});
  useEffect(() => {
    getProviders().then(setProviders);
  }, []);
  return (
    <div className="max-w-sm mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">登录</h1>
      {Object.values(providers).map((provider: any) => (
        <div key={provider.name} className="mb-2">
          <button
            className="w-full bg-blue-600 text-white py-2 rounded"
            onClick={() => signIn(provider.id)}
          >
            使用 {provider.name} 登录
          </button>
        </div>
      ))}
    </div>
  );
}

3. 受保护页面与 Session 校验

tsx 复制代码
// pages/dashboard.tsx
import { getSession, signOut } from 'next-auth/react';
import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [session, setSession] = useState<any>(null);
  useEffect(() => {
    getSession().then(setSession);
  }, []);
  if (!session) return <div>请先登录</div>;
  return (
    <div className="p-4">
      <h2>欢迎,{session.user?.name}</h2>
      <button onClick={() => signOut()} className="mt-4 bg-gray-200 px-4 py-2 rounded">退出登录</button>
    </div>
  );
}

4. API Route 保护与权限校验

ts 复制代码
// pages/api/protected.ts
import { getSession } from 'next-auth/react';

export default async function handler(req, res) {
  const session = await getSession({ req });
  if (!session) {
    return res.status(401).json({ error: '未认证' });
  }
  // 权限校验
  if (session.user.role !== 'admin') {
    return res.status(403).json({ error: '无权限' });
  }
  res.json({ data: '受保护数据' });
}

5. App Router 下的 Server Component/Action 认证

ts 复制代码
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '../api/auth/[...nextauth]/route';

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);
  if (!session) return <div>请先登录</div>;
  return <div>欢迎,{session.user?.name}</div>;
}

理论讲解(进阶扩展)

6. OAuth2/OpenID Connect 流程与 Token 类型

  • OAuth2 授权码流程:用户点击第三方登录,跳转到 Provider 授权页,授权后回调,后端用 code 换取 AccessToken/IDToken。
  • OpenID Connect:OAuth2 的扩展,增加 IDToken(JWT 格式),用于身份认证。
  • Token 类型
    • AccessToken:访问资源 API 的令牌,短时有效。
    • RefreshToken:用于刷新 AccessToken,安全性要求高。
    • IDToken:OpenID Connect 用于身份认证的 JWT。
  • 多端同步与 SSO:可结合企业微信、Google、LDAP、AD 实现单点登录,支持多端同步 Session。
  • 多因子认证(MFA):可集成短信、邮箱、TOTP 二次验证,提升安全性。

7. NextAuth.js 回调机制与权限模型

  • 回调机制
    • signIn:登录前回调,可自定义登录逻辑、拒绝登录。
    • redirect:登录/登出后跳转。
    • session:扩展 session 字段,如 userId、role、权限等。
    • jwt:自定义 JWT 内容,如权限、过期时间。
    • authorize:自定义凭证登录逻辑。
    • events:登录、登出、错误等事件钩子,可做日志采集、审计。
  • 权限模型
    • RBAC(基于角色的访问控制):user.role 字段控制页面/接口权限。
    • ABAC(基于属性的访问控制):结合用户属性、资源属性、环境属性做细粒度权限。

8. 安全威胁与防护

  • 会话劫持:Session 建议用 HttpOnly、Secure、SameSite Cookie,防止 XSS/CSRF。
  • CSRF 防护:NextAuth.js 内置 CSRF Token 校验,表单/接口需带 Token。
  • XSS 防护:所有用户输入需转义,前端渲染时注意危险 HTML。
  • Token 失效与刷新:AccessToken 过期后用 RefreshToken 刷新,Session 失效自动登出。
  • 异常监控:集成 Sentry、Datadog 等平台,监控认证异常、登录失败、权限拒绝等。

复杂代码示例与企业级场景

1. 邮箱验证码登录与注册激活

ts 复制代码
// pages/api/auth/email-login.ts
import { sendEmailCode, verifyEmailCode } from '@/lib/email';

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { email, code } = req.body;
    if (!code) {
      // 发送验证码
      await sendEmailCode(email);
      return res.json({ ok: true });
    } else {
      // 校验验证码
      const valid = await verifyEmailCode(email, code);
      if (!valid) return res.status(400).json({ error: '验证码错误' });
      // 生成 Session 或返回 Token
      // ...
      return res.json({ ok: true });
    }
  }
  res.status(405).end();
}

2. 短信验证码与多因子认证(MFA)

ts 复制代码
// pages/api/auth/sms-login.ts
import { sendSmsCode, verifySmsCode } from '@/lib/sms';

export default async function handler(req, res) {
  // 类似邮箱验证码,略
}

3. 用户注册、激活与找回密码

ts 复制代码
// pages/api/auth/register.ts
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { email, password } = req.body;
    // 校验、写入数据库、发送激活邮件
    // ...
    res.json({ ok: true });
  }
  res.status(405).end();
}

// pages/api/auth/activate.ts
export default async function handler(req, res) {
  // 校验激活链接,激活用户
}

// pages/api/auth/forgot-password.ts
export default async function handler(req, res) {
  // 发送重置密码邮件,校验重置链接
}

4. Session 持久化与 Token 刷新

ts 复制代码
// next-auth 配置中
session: {
  strategy: 'jwt',
  maxAge: 60 * 60 * 24 * 7, // 7天
  updateAge: 60 * 60 * 2, // 2小时自动刷新
},
// ...
callbacks: {
  async jwt({ token, account }) {
    if (account && account.refresh_token) {
      token.refreshToken = account.refresh_token;
    }
    // 刷新逻辑略
    return token;
  },
}

5. 企业微信/Google 登录与多 Provider 配置

ts 复制代码
import WechatWorkProvider from 'next-auth/providers/wechat-work';
import GoogleProvider from 'next-auth/providers/google';

providers: [
  // ...
  WechatWorkProvider({
    clientId: process.env.WECHATWORK_CLIENT_ID!,
    clientSecret: process.env.WECHATWORK_CLIENT_SECRET!,
  }),
  GoogleProvider({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
]

6. App Router 下 Server Actions 保护敏感操作

ts 复制代码
// app/api/protected/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '../auth/[...nextauth]/route';

export async function POST(req) {
  const session = await getServerSession(authOptions);
  if (!session || session.user.role !== 'admin') {
    return new Response('无权限', { status: 403 });
  }
  // 业务逻辑
  return new Response('ok');
}

7. 日志采集与异常监控

ts 复制代码
import * as Sentry from '@sentry/nextjs';

callbacks: {
  async signIn({ user, account, profile }) {
    Sentry.captureMessage(`用户登录: ${user.email}`);
    return true;
  },
  async signOut({ token }) {
    Sentry.captureMessage(`用户登出: ${token.email}`);
    return true;
  },
  async error({ error }) {
    Sentry.captureException(error);
    return true;
  },
}

企业级实战案例(扩展)

1. 权限分级与用户管理

  • 用户分为超级管理员、管理员、普通用户、访客等,权限字段写入 session/jwt。
  • 用户管理后台可增删改查用户、分配角色、重置密码、禁用账号。
  • 所有敏感操作需二次确认与日志审计。

2. 与企业认证系统集成

  • 支持企业微信、钉钉、LDAP、AD 等企业 SSO。
  • 可用 next-auth 的自定义 Provider 或 OAuth2/OpenID Connect 标准协议对接。
  • 支持与微服务架构下的统一认证中心集成。

3. 操作日志与异常监控

  • 所有登录、登出、权限变更、异常操作均写入日志,便于安全审计。
  • 集成 Sentry、Datadog、ELK 等平台,实时监控认证与授权相关异常。

最佳实践(扩展)

  • 安全:所有接口、页面、Server Action 必须做 session/权限校验,防止越权。
  • 性能:Session 建议用 JWT,减少数据库压力,Token 过期及时刷新。
  • 合规:用户数据加密存储,敏感信息脱敏,符合 GDPR/等保等合规要求。
  • 团队协作:统一认证/授权中间件,约定接口/页面保护规范,便于多人协作。
  • 测试:为认证、授权、注册、找回密码等关键流程编写单元与端到端测试。
  • 监控:认证相关操作、异常、权限变更均需日志采集与监控。
  • 移动端适配:认证流程、验证码、MFA、扫码登录等需兼容移动端。
  • a11y/国际化:所有表单、按钮、提示信息需支持无障碍和多语言。

常见问题与解决方案(扩展)

Q7: 如何实现单点登录(SSO)?

A: 结合企业微信、Google、LDAP、AD 等 Provider,或用 OpenID Connect 协议对接统一认证中心。

Q8: 如何做多因子认证(MFA)?

A: 登录后要求输入短信/邮箱验证码或 TOTP,后端校验通过后再生成 session。

Q9: 如何防止 Token/Session 被劫持?

A: 设置 HttpOnly、Secure、SameSite Cookie,定期刷新 Token,异常登录自动失效。

Q10: 如何做权限细粒度控制?

A: 在 session/jwt 中写入权限字段,前后端均做权限校验,结合 RBAC/ABAC。

Q11: 如何与微服务架构协作?

A: 认证中心统一颁发 Token,各微服务校验 Token 并做权限控制。

Q12: 如何处理 Provider 扩展与自定义?

A: 可用 next-auth 的自定义 Provider,或基于 OAuth2/OpenID Connect 标准协议扩展。

Q13: 如何做异常处理与用户体验优化?

A: 登录失败、权限拒绝、Token 失效等需友好提示,后端记录异常日志,前端自动跳转。


配图说明(扩展)

graph TD; A[用户点击登录] --> B[OAuth2/SSO 授权流程] B --> C[Provider 授权/凭证校验] C --> D[生成 AccessToken/IDToken/Session] D --> E[前端获取 Session/Token] E --> F[页面/接口权限校验] F --> G[Token 刷新/Session 续期]

OAuth2/SSO 认证与授权全流程、Token 刷新与权限校验示意图。
最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
风无雨25 分钟前
GO启动一个视频下载接口 前端可以边下边放
前端·golang·音视频
aha-凯心1 小时前
前端学习 vben 之 axios interceptors
前端·学习
熊出没1 小时前
Vue前端导出页面为PDF文件
前端·vue.js·pdf
VOLUN1 小时前
Vue3项目中优雅封装API基础接口:getBaseApi设计解析
前端·vue.js·api
用户99045017780092 小时前
告别广告干扰,体验极简 JSON 格式化——这款工具让你专注代码本身
前端
前端极客探险家2 小时前
告别卡顿与慢响应!现代 Web 应用性能优化:从前端渲染到后端算法的全面提速指南
前端·算法·性能优化
袁煦丞2 小时前
【局域网秒传神器】LocalSend:cpolar内网穿透实验室第418个成功挑战
前端·程序员·远程工作
江城开朗的豌豆2 小时前
Vuex数据突然消失?六招教你轻松找回来!
前端·javascript·vue.js
好奇心笔记3 小时前
ai写代码随机拉大的,所以我准备给AI出一个设计规范
前端·javascript
江城开朗的豌豆3 小时前
Vue状态管理进阶:数据到底是怎么"跑"的?
前端·javascript·vue.js