【Next.js 入门教程系列】07-身份验证

原文链接

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的点个star,关注一下吧

上一篇【Next.js 入门教程系列】06-上传文件

身份验证

本篇包括以下内容:

  • Setting up Next Auth
  • Configuring the Google Provider
  • Authentication sessions
  • Protecting routes
  • Database adapters
  • Configuring the Credentials Provider

Setting up Next Auth

使用 npm install next-auth 安装 Next-Auth(Auth.js)。安装好后首先去 .env 中配置环境变量

DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成

设置完成后,在 api 文件夹下添加 auth/[...nextauth]/route.ts,并添加以下内容,为接下来做准备

api\auth[...nextauth]\route.ts

import NextAuth from "next-auth/next";

const handler = NextAuth({});

export { handler as GET, handler as POST };

设置 Provider

本章代码链接

Next Auth Provider可以看到 Next-Auth 支持多个 Authenticator Provider,包括 GoogleGithubFacebook

创建项目

此处以 Google 为例,首先进入 Google Credentials 页面新建一个 Project

设置项目名称后点击创建即可

如下图,点击配置 Consent Screen

首先设置为供外部使用

在弹出页面填写三个必须字段 应用名称用户支持电子邮件,以及最下面的开发者联系信息,其他字段都可选填,比如图片 logo 之类的

点击页面最下面的保存并继续,设置应用权限,一般只添加 email 和 profile 即可。再点击保存并继续,添加测试用户,把自己的账户添加即可。

最后保存返回信息中心即可

创建 OAuth

凭据 页面点击创建 OAuth 客户端 ID

设置 OAuth,首先设置为 Web 应用,设置应用名,下方 JavaScript 来源设置为部署的端口(开发环境),再下方设置重定向的 URI,在Next Auth Google页面有写

点击创建即可

复制好客户端 id 和密钥备用

调用

回到 .env 中添加 GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET

DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成
# 刚刚的客户端id 和 密钥
GOOGLE_CLIENT_ID=479267153395-gpqe25rbf62p0aj4h7icnfd01qt0p3qo.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

在 auth/[...nextauth]/route.ts,并添加以下内容(直接复制即可)

api\auth[...nextauth]\route.ts

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
});

export { handler as GET, handler as POST };

在 Navbar.tsx 中添加一个 Link 到 login

NavBar.tsx

import Link from "next/link";
import React from "react";

const NavBar = () => {
  return (
    <div className="flex bg-slate-200 p-5 space-x-3">
      <Link href="/" className="mr-5">
        Next.js
      </Link>
      <Link href="/users">Users</Link>
      {/* Add this Link */}
      <Link href="/api/auth/signin">Login</Link>
    </div>
  );
};

export default NavBar;

回到浏览器,点击 login,即可看到如下页面


由于某些魔法原因,next-auth 使用 google 一直在报 ERROR,笔者寻找多方也未解决,尝试使用 Github Provider 可行,故向读者推荐使用 Github Provider,并在下面给出演示

[next-auth][error][SIGNIN_OAUTH_ERROR]
https://next-auth.js.org/errors#signin_oauth_error outgoing request timed out after 3500ms {
  error: {
    message: 'outgoing request timed out after 3500ms',
    stack: 'RPError: outgoing request timed out after 3500ms\n' +
      .......
  name: 'RPError'
  },
  providerId: 'google',
  message: 'outgoing request timed out after 3500ms'
}

Github Provider

进入Github OAuth App页面,新建 OAuth App

设置内容和 Google 内容差不多,Callback url 最后换成 Github 即可 http://localhost:5050/api/auth/callback/github

点击创建凭证

IMPORTANT

注意,创建好后仅能在该页面复制一次,以后再也无法复制

回到 .env 中添加 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET

DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成
# 刚刚的客户端id 和 密钥
GITHUB_CLIENT_ID=6f1433456dsfa526c
GITHUB_CLIENT_SECRET=ec8055183a9adfefsf0b305c282be6d55fe64f

修改 auth/[...nextauth]/route.ts 中的 provider 为 Github

import NextAuth from "next-auth";
import Github from "next-auth/providers/github";

const handler = NextAuth({
  providers: [
    Github({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
});

export { handler as GET, handler as POST };

保存,进入 Login 界面即可正常使用 Github 登录

Session

Check Session

本章代码链接

在浏览器中打开开发者工具,进入应用,选择 cookie,即可看到这里有一条 next-auth.session-token ,其本质为一个 json web token,为了查看这个 cookie,我们可以在 /api/auth 下新建 token/route.ts,并添加以下内容

import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const token = await getToken({ req: request });
  return NextResponse.json(token);
}

去浏览器访问 http://localhost:5050/api/auth/token 即可看到如下内容

其包含如用户名,邮箱,头像,过期时间等信息

Accessing Session from client

本章代码链接

在用户端获取 session 信息需要用到 SessionProvider。我们首先创建一个新 component /auth/AuthProvider.tsx。将所有 children 用 SessionProvider 包起来

"use client";
import React, { ReactNode } from "react";
import { SessionProvider } from "next-auth/react";

const AuthProvider = ({ children }: { children: ReactNode }) => {
  return (
    <>
      <SessionProvider>{children}</SessionProvider>
    </>
  );
};
export default AuthProvider;

然后回到根文件夹的 layout.tsx

  import "./globals.css";
  import type { Metadata } from "next";
  import { Inter } from "next/font/google";
  import NavBar from "./NavBar";
// 导入刚刚的 AuthProvider
+ import AuthProvider from "./auth/Provider";

  const inter = Inter({ subsets: ["latin"] });

  export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
  };

  export default function RootLayout({
    children,
  }: {
    children: React.ReactNode;
  }) {
    return (
      <html lang="en" data-theme="winter">
        <body className={inter.className}>
{/*将 body 里的内容都用 AuthProvider 包起来*/}
+         <AuthProvider>
            <NavBar />
            <main className="p-5">{children}</main>
+         </AuthProvider>
        </body>
      </html>
    );
  }

之后到 NavBar 组件里获取 Session 内容

  "use client";
  // improt useSession
+ import { useSession } from "next-auth/react";
  import Link from "next/link";
  import React from "react";

  const NavBar = () => {
  // 使用 useSession() 来获取 Session 中的数据
+   const { status, data: session } = useSession();

    return (
      <div className="flex bg-slate-200 p-5 space-x-3">
        <Link href="/" className="mr-5">
          Next.js
        </Link>
        <Link href="/users">Users</Link>
  {/*根据status的不同状态来渲染 "登录" 或者 "用户" 或 "加载中"*/}
+       {status === "loading" && <div>Loading...</div>}
+       {status === "authenticated" && <div>{session.user!.name}</div>}
+       {status === "unauthenticated" && (
+         <Link href="/api/auth/signin">Login</Link>
+       )}
      </div>
    );
  };

  export default NavBar;

此时回到浏览器,刷新即可看到,可以正常显示用户名了

Accessing Session from server

本章代码链接

在服务器端获取 session 也很简单,首先要先回到 api/auth/[...nextauth]/route.ts 中修改一下,将刚刚的 providers 作为一个 const export 出来,以便在其他页面使用(注意笔者使用的还是 Github 作为 Provider)

import NextAuth from "next-auth";
import Github from "next-auth/providers/github";
// import GoogleProvider from "next-auth/providers/google";

export const authOptions = {
  providers: [
    Github({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

然后在主页面的 page.tsx 中可以调用 getServerSession() 来获取 session

  import Link from "next/link";
  import ProductCard from "./components/ProductCard/ProductCard";
  // 导入 getServerSession 方法和刚刚的 authOptions 设置
+ import { getServerSession } from "next-auth";
+ import { authOptions } from "./api/auth/[...nextauth]/route";

  export default async function Home() {
  // 调用 getServerSession 来获取 session
+   const session = await getServerSession(authOptions);
    return (
      <>
        <main>
  {/*直接调用session中的内容(user!中的!代表该变量不会为空)"*/}
+         <h1>Hello {session && session.user!.name}!</h1>
          <Link href="/users">Users</Link>
          <ProductCard />
        </main>
      </>
    );
  }

最终显示效果如下

Sign Out

本章代码链接

使用一个 Link 跳转到 api/auth/signout 即可

  "use client";

  import { useSession } from "next-auth/react";
  import Link from "next/link";
  import React from "react";

  const NavBar = () => {
    const { status, data: session } = useSession();

    return (
      <div className="flex bg-slate-200 p-5 space-x-3">
        <Link href="/" className="mr-5">
          Next.js
        </Link>
        <Link href="/users">Users</Link>
        {status === "loading" && <div>Loading...</div>}
        {/*跳转至 api/auth/signout 即可*/}
+       {status === "authenticated" && (
+         <div>
+           {session.user!.name}
+           <Link href="api/auth/signout" className="ml-3">
+             Sign Out
+           </Link>
+         </div>
+       )}
        {status === "unauthenticated" && (
          <Link href="/api/auth/signin">Login</Link>
        )}
      </div>
    );
  };

  export default NavBar;

最终效果如下

Protecting Route

本章代码链接

有时候,我们需要防止用户在没有登录的情况下跳转至某些页面,比如想要直接使用 url 进入到 profile 页面,此时我们需要重定向到登录界面。在 Next.js 中内置了 MiddleWare 帮我们完成这个任务,我们不需要手动在每个界面自己写跳转。我们只需要在根目录(注意是和 app 同级目录,之前都是在 app 文件夹中)下添加 middleware.ts 添加设置即可,如下代码则表示所有以 /dashboard 开头的路由,都需要有 session。其最后一个字符代表子路由的层级

export { default } from "next-auth/middleware";

export const config = {
  // *: zero or more
  // +: one or more
  // ?: zero or one
  matcher: ["/dashboard/:path*"],
};

Database Adapters

本章代码链接

Next-Auth Prisma页面可以找到,使用 npm i @next-auth/prisma-adapter 以安装 adapter

备注

Next-Auth 正在改名为 Auth.js,截止 2024.2.27,仍然可以使用上方 npm 指令安装,如果读者使用时出错,访问Next-Auth Prisma页面应该可以找到新的安装命令

安装好之后,配置 schema.prisma,同样在Next-Auth Prisma Schema页面可以找到教程,也可以把我下面的代码直接复制到 schema.prisma 中,再使用 npx prisma migrate dev 指令进行合并即可

schema.prisma配置

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

完成后在 api\auth[...nextauth]\route.ts 设置 PrismaAdapter

  import NextAuth, { NextAuthOptions } from "next-auth";
  import Github from "next-auth/providers/github";
  import GoogleProvider from "next-auth/providers/google";
+ import { PrismaAdapter } from "@next-auth/prisma-adapter";
+ import prisma from "@/prisma/client";

  export const authOptions: NextAuthOptions = {
  // 设置 PrismaAdapter
+   adapter: PrismaAdapter(prisma),
    providers: [
      GoogleProvider({
        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!,
      }),
    ],
    session: {
      strategy: "jwt",
    },
  };

  const handler = NextAuth(authOptions);

  export { handler as GET, handler as POST };

再次尝试登陆后,即可在数据库中看到

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的点个star,关注一下吧

下一篇讲发送邮件

下一篇【Next.js 入门教程系列】08-发送邮件

相关推荐
Hacker_LaoYi37 分钟前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀39 分钟前
Redis梳理
数据库·redis·缓存
独行soc39 分钟前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天1 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
工业甲酰苯胺1 小时前
分布式系统架构:服务容错
数据库·架构
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain3 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship3 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站3 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶3 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb