03 身份验证⚓︎
Clerk | Authentication and User Management
该节参考Add authentication and user management to your Next.js app with Clerk in 7 minutes | Clerk
使用Clerk为Next.js应用程序添加身份验证和用户管理功能⚓︎
安装@clerk/nextjs⚓︎
Add authentication and user management to your Next.js app with Clerk in 7 minutes | Clerk
npm install @clerk/nextjs
Clerk的Next.js SDK可以访问Next.js服务器组件、路由处理程序和中间件的预构建components, hooks, 和 helpers 。
创建项目⚓︎
使用谷歌账号注册后登录
设置认证时需要的登录方式
创建项目后得到一个API Key(公钥和私钥)
设置环境密钥⚓︎
把上面生成的密钥复制到nextjs项目下的.env
(新建)文件中
可在Clerk Dashboard查看密钥
因为后面要使用prisma,所以使用
.env
而不是.env.local
.env是什么?
.env是一个文件,用于存储环境变量,例如API密钥,数据库连接字符串等。环境变量是一些可以在不同的环境中改变的值,例如开发环境,测试环境,生产环境等。使用环境变量可以让你的代码更灵活,更安全,更易于维护。
Next.js支持从.env.local文件中加载环境变量到process.env对象中,这样你就可以在Next.js的数据获取方法和API路由中使用它们。例如,你可以在.env.local文件中定义一个API密钥,然后在getStaticProps方法中使用它来调用一个外部的API。
如果你想让你的环境变量在浏览器中也可用,你需要在变量名前加上NEXT_PUBLIC_的前缀。这样,Next.js会在构建时把这些变量内联到客户端的js包中,替换掉所有对process.env.[variable]的引用。例如,你可以在.env.local文件中定义一个NEXT_PUBLIC_ANALYTICS_ID变量,然后在你的代码中任何地方使用它。
.env.local
也行
Next.js 项目中的 .env.local 和 .env 文件都是用来存储环境变量的,但是它们有一些区别:
.env
文件是用来设置所有环境(development, production, test)下的默认环境变量的,它应该被提交到源码仓库中。.env.local
文件是用来覆盖.env
文件中的默认环境变量的,它应该被添加到.gitignore
中,因为它可能包含一些敏感信息,比如数据库密码。.env.local
文件只在开发环境(next dev)和生产环境(next start)中生效,而不会在测试环境(next test)中生效。
开发、生产、测试环境
开发环境、生产环境和测试环境是指运行 Next.js 项目的不同模式,它们有不同的目的和特点:
- 开发环境(next dev)是用来在本地开发和调试 Next.js 项目的,它支持热重载、快速刷新和错误提示等功能,可以帮助你快速开发和修改代码。例如,你可以在终端中输入
next dev
命令来启动开发环境,然后在浏览器中访问http://localhost:3000
来查看你的项目。 - 生产环境(next start)是用来部署和运行 Next.js 项目的,它会对项目进行优化和打包,提高性能和安全性,适合用于正式上线的网站。例如,你可以在终端中输入
next build
命令来构建你的项目,然后输入next start
命令来启动生产环境,然后在浏览器中访问你的域名来访问你的网站。 - 测试环境(next test)是用来对 Next.js 项目进行单元测试或集成测试的,它可以检查你的代码是否符合预期的功能和逻辑,避免出现错误和bug。例如,你可以使用 jest 或 cypress 等工具来编写和运行测试用例,然后在终端中输入
next test
命令来启动测试环境,然后查看测试结果和报告。
使用<ClerkProvider/>
包装APP⚓︎
<ClerkProvider>
组件为Clerk的钩子和其他组件提供活动会话和用户上下文。
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ClerkProvider } from "@clerk/nextjs";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Ean",
description: "AI平台",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</ClerkProvider>
);
}
使活动会话和用户上下文可以在app中的任何位置访问。
需要身份验证才能访问app⚓︎
Clerk已经安装在应用程序中,可以决定哪些页面是公共的,哪些页面需要身份验证才能访问。
新建一个在 middleware.ts
,根目录上和.env
一起
import { authMiddleware } from "@clerk/nextjs";
// 此示例保护所有路由,包括api/trpc路由
// 请编辑此项以允许其他路由根据需要公开。
// 查看https://clerk.com/docs/references/nextjs/auth-middleware有关配置中间件的详细信息
export default authMiddleware({});
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};
现在可以通过http://localhost:3000 来访问应用程序。
中间件会将您重定向到由Clerk的 Account Portal功能提供的注册页面。如果您想公开其他路由,请查看authMiddleware参考页。
注册登陆后会跳转到首页
Middleware中间件
https://nextjs.org/docs/pages/building-your-application/routing/middleware
middleware.ts 是 Next.js 项目中用来定义中间件的文件,中间件可以让你在请求完成之前运行一些代码,比如重写、重定向、修改请求或响应头,或者直接响。中间件的文件名必须是 middleware.ts(或 .js),并且放在项目的根目录下,和 pages 或 app 同级。 可以通过 config 对象来指定中间件匹配的路径,使用正则表达式或命名参数。例如,下面的中间件只会匹配 /about 路径下的请求:
export const config = {
matcher: '/about/:path*',
}
嵌入 <UserButton />
⚓︎
Clerk提供了一套预构建的组件,可以最大限度地为应用程序添加功能。 <UserButton />
允许用户管理其帐户信息和注销,完成完整的身份验证周期。
在应用程序的<ClerkProvider/>
中任何地方都可以添加它。
afterSignOutUrl
属性设置自定义用户注销后将重定向到的页面。
可以放在登录后的控制面板中
import { UserButton } from "@clerk/nextjs";
const DashboardPage = () => {
return (
<div>
<p>控制面板(受保护)</p>
<UserButton afterSignOutUrl="/" />
</div>
);
};
export default DashboardPage;
注销应用程序⚓︎
测试一下退出后回到的页面
使用Clerk为Next.js应用程序构建自己的登录和注册页面⚓︎
Clerk提供了一套预构建的组件和自定义流程,可以将它们用于将登录、注册和其他用户管理功能嵌入到Next.js应用程序中。
将
建立注册页面⚓︎
import { SignUp } from "@clerk/nextjs";
export default function Page() {
return <SignUp />;
}
[[...sign-up]]
在 Next.js 中,[[...]]
用于文件名是用来表示动态路由的一种特殊语法。Next.js 的路由是基于页面文件系统的,当你在 pages
目录下创建文件时,这个文件的路径就对应于一个路由。
这里的 [[...sign-up]]
表示一个“捕获所有路由”的动态路由。具体来说:
[]
表示该部分是动态的。例如,pages/[username].tsx
会匹配/alice
或/bob
,其中username
是动态变化的部分。...
是一个展开运算符,用在动态路由中表示它可以捕获所有包含零个或多个路由段的路径。[[...]]
双括号表示这个动态路由是可选的。也就是说,如果你有一个pages/post/[[...slug]].tsx
文件,它既可以匹配/post
,也可以匹配更深的路径,如/post/2023/03/30
。
所以,pages/[[...sign-up]].tsx
会匹配任何以 /
开始,并在 /sign-up
后面有任何内容(或没有内容)的路由。这对于创建具有多个子路径的页面非常有用,但是你又不想为每个子路径单独创建一个页面文件。
例如:
- 访问
/
可以被该路由捕获。 - 访问
/sign-up
也可以被该路由捕获。 - 访问
/sign-up/extra/info
同样可以被该路由捕获。
在页面组件中,你可以通过 Next.js 的 useRouter
钩子获取动态路径段的值。
建立登录页面⚓︎
import { SignIn } from "@clerk/nextjs";
export default function Page() {
return <SignIn />;
}
文件结构
更新环境变量⚓︎
为signIn
、signUp
、afterSignUp
和afterSignIn
路径添加环境变量:
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
当登录或注册时,以及当单击每个组件底部的相应链接时,这些值控制组件的行为。
访问网站⚓︎
localhost:3000/sign-in 和 localhost:3000/sign-up.
以及登陆后进入到dashboard
由于有中间件的存在,访问
localhost:3000/
下的任何路由都会出现登录框。包括localhost:3000/
所以可以设置公共的路由
publicRoutes
来去除掉某些网址middleware.tsimport { authMiddleware } from "@clerk/nextjs"; // This example protects all routes including api/trpc routes // Please edit this to allow other routes to be public as needed. // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware export default authMiddleware({ publicRoutes: ["/"] }); export const config = { matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], };
补充⚓︎
调整布局⚓︎
调整前:
const AuthLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex items-center justify-center h-full ">{children}</div>
);
};
export default AuthLayout;
代码解释
这段代码定义了一个名为 AuthLayout
的 React 函数组件,它是一个布局组件,用于包裹传递给它的子组件。在这个 AuthLayout
组件中,children
是一个特殊的 prop,表示那些嵌套在组件内部的 JSX 元素。
让我们一步步分析这段代码:
-
const AuthLayout = ({ children }: { children: React.ReactNode }) => { ... };
这一行定义了一个常量AuthLayout
,它是一个箭头函数组件。这个组件接收一个对象作为参数,该对象包含一个children
属性,类型为React.ReactNode
。React.ReactNode
是 React 类型定义的一部分,它可以是任何可渲染的 React 内容,包括null
、boolean
、number
、string
、ReactElement
或这些类型的数组。 -
<div className="flex items-center justify-center h-full ">{children}</div>
这里返回了一个div
元素,它的类名设置了一些 Tailwind CSS 的工具类:flex
: 应用了 Flexbox 布局,使得div
的子元素可以使用 Flexbox 布局属性。items-center
: 用来在垂直方向上将子元素居中对齐。justify-center
: 在水平方向上将子元素居中对齐。h-full
: 设置div
的高度为父元素的高度。
这个 div
使用了 Flexbox 居中布局,确保任何作为 children
传递给 AuthLayout
的内容都会在页面上水平和垂直居中显示。
-
{children}
这是 JSX 中的表达式,用大括号包裹。这里它表示AuthLayout
组件的子元素。这样,任何放在<AuthLayout>...</AuthLayout>
标签中的内容都会被渲染到这个div
中的相应位置。 -
export default AuthLayout;
最后这行代码将AuthLayout
组件作为默认导出。这意味着在其他文件中可以不用花括号直接导入这个布局组件。
原理:layout.tsx作用于(auth)
下的所有页面
在 Next.js 中,如果你看到 app/(auth)/(routes)
这样的路径,并且这个路径下的页面都采用了 app/(auth)/layout.tsx
的布局,这是因为 Next.js 的路由系统提供了一种机制,可以让你为特定的路由群组指定共用的布局组件。
这里的原理是:
布局继承:
-
app/(auth)/layout.tsx
作为app/(auth)/
路径下所有页面的默认布局。 -
如果存在嵌套的路由路径,如
app/(auth)/(routes)/
,且没有为(routes)
指定专属的布局组件,则(routes)
下的页面将继承其父路由(auth)
的布局组件。
目录结构与路由匹配:
-
Next.js 会根据
app
目录下的文件结构自动建立路由。 -
路由的嵌套层级决定了页面如何继承布局。因此,
app/(auth)/(routes)
下的页面会继承app/(auth)/layout.tsx
的布局,除非你在(routes)
目录下指定了一个不同的布局组件。
自动页面组装:
-
当构建应用时,Next.js 会查看你的
app
目录并自动把页面、布局和其他组件组装起来。 -
布局组件相当于页面的一个父组件,当渲染页面时,页面将作为布局组件的子组件插入。
通过这种方式,Next.js 允许开发者创建复杂的布局继承结构,使得布局的管理变得更加模块化和灵活。这种方法非常适合大型应用,其中某些页面部分可能需要共享同样的基础布局,而其他部分则需要有所不同。
必须用layout.tsx,不能改名,否则不起作用
命名规范
在 Next.js 的 app
目录结构中,特定的命名约定确实对文件和组件的行为有影响。例如,布局组件通常被命名为 layout.tsx
是因为这是 Next.js 的默认行为,它会自动将这个组件作为其子目录中页面的布局。
如果你将 layout.tsx
改名为其他名称,这个自动应用布局的行为将不再适用。Next.js 不会自动识别一个不遵循命名约定的文件作为布局文件。然而,这并不意味着你不能使用一个不同名字的布局组件,只是你需要手动指定页面应当使用哪个布局组件。
例如,如果你将 layout.tsx
重命名为 CustomLayout.tsx
,你需要在每个页面组件中显式地导入并使用这个布局组件:
import CustomLayout from './CustomLayout';
const Page = () => {
return (
<CustomLayout>
{/* 页面内容 */}
</CustomLayout>
);
};
export default Page;
这样做,你就可以使用任何名字的布局组件,但需要牢记的是,不再有自动应用布局的便利性,需要在每个页面组件中都做这样的导入和布局应用。这种方式提供了更高的自由度和灵活性,但也增加了维护成本,尤其是在大型项目中,每个页面都需要明确指定布局组件。
调整后:
首页登录注册按钮⚓︎
import { Button } from "@/components/ui/button";
import { UserButton } from "@clerk/nextjs";
import Link from "next/link";
export default function Home() {
return (
<div>
<div>首页</div>
<Link href="/sign-in">
<Button>登录</Button>{" "}
</Link>
<Link href="/sign-up">
<Button>注册</Button>{" "}
</Link>
</div>
);
}