- 使用 Supabase Auth 和 Google 登录功能构建登录和注册流程,以控制谁可以访问您的应用
- 在 Express.js 后端中添加身份验证中间件,以保护您的 API 路由免受未经授权的请求
- 测试前端和后端的安全性,以确保只有已登录的用户才能触发您的 AI 功能
- 用于创建用户账户并保持用户登录状态的注册和登录页面
- 通过 后端中间件保护的 API 路由 ,该中间件会在处理请求前验证身份验证令牌
学生们已在 GitHub 仓库中构建了一个经过调试的最小可行产品(MVP),其中包含 React 前端和 Express.js 后端。在前端和后端可能作为两个独立进程在 Codespaces 中运行。
通过身份验证保障应用程序的安全
目前,任何拥有您的 Codespace 链接的人都可以使用您的应用。系统没有登录界面,您的 Express.js 后端会无条件处理收到的每个请求。如果有人发现了您的 URL,他们就可以触发 AI 调用,这会给您带来费用。正如您在制定财务计划时所了解的那样,每次 API 调用都会产生费用。保护这些调用不仅是一个技术决策,更是一个商业决策。
在本单元中,我们将以TutorMatch作为指导示例。你将按照 TutorMatch 的示例操作,然后针对自己的最小可行产品(MVP)调整每个步骤。
该设备增加了两层保护。
- 用户身份验证——设置登录界面,确保只有注册用户才能查看您的应用。
- 安全检查 在您的后端中,确保对每个 API 请求进行验证,以确认其确实来自已登录的用户。
不妨这样理解:登录页面就像是保护“大门”的屏障,后端安全检查则像保护“窗户”的屏障。二者结合,意味着您的应用程序从两方面都得到了保护。
为什么不自己保存密码呢?
处理用户账户最直观的方法也是最危险的:创建一个用户表,在用户注册时存储其电子邮件和密码,并在用户登录时验证密码。
问题在于,这种方式会将密码以明文形式存储,任何访问您数据库的人都能读取。一旦您的数据库遭到泄露,所有用户的密码都会面临泄露风险。专业级应用绝不会这样做。
安全的身份验证需要以下几个方面协同工作:
- 密码哈希—— 在存储前将密码转换为不可读的字符串
- 会话令牌—— 一种临时标识符,可让用户保持登录状态,而无需在每个页面都输入密码
- 安全存储——对凭证进行加密处理,确保其在传输或静止状态下均不会泄露。
从头开始构建所有这些内容既复杂又容易出错。这就是身份验证服务存在的意义——它们负责处理安全问题,让你能够专注于自己的应用。
在选择用户身份验证服务时,有以下几种选项:
选项
工作原理
免费层级
复杂性
结论
自行保存密码
创建用户表,对密码进行哈希处理,手动管理会话
免费
高——安全失误很容易发生,且后果严重
不推荐用于MVP
Google 登录(OAuth)
用户点击“使用 Google 登录”。所有安全事宜均由 Google 负责。
免费
中等 — 需要配置 Google Cloud Console
这是一个不错的选择,但需要多几个设置步骤
Auth0 / 文员
带有预置登录页面的第三方身份验证服务
提供免费套餐
Medium — 需要注册新账号并安装 SDK
不错,但需要管理一项额外服务
Supabase 身份验证
内置于 Supabase 中。自动处理哈希、会话和存储。
免费(已包含)
Low —— 您已有 Supabase 账户
MVP 的绝佳选择
在本节课中,我们将使用 Supabase Auth 进行用户身份验证。此外,我们还将添加 Google 登录功能,因为这是专业应用的标准登录选项,且配置起来并不太难。选择 Supabase Auth 主要有以下三个原因:
- 您已经通过“创始人工具包”拥有了一个 Supabase 账户。
- Supabase 开箱即用,即可处理上述所有复杂的安全问题。
- 免费套餐涵盖了您开发最小可行产品(MVP)所需的一切。
还有一点需要考虑:Supabase Auth 支持行级安全(RLS), 即您可配置的数据库 规则, 用于过滤数据,确保每位用户只能查看自己的记录。我们在将 Supabase 添加到“创始人工具包”时曾简要提及过这一点。不过,RLS 无法保护您的 AI 路由。因此,我们需要添加进一步的保护措施。
“现阶段无需使用 RLS,因为我们没有创建任何自定义表。Supabase 会在内部处理用户数据的存储。如果你在最小可行产品(MVP)中存储用户特定数据——例如保存用户的历史记录、偏好设置或交易记录——那么 RLS 就会派上用场。”
Supabase 身份验证
创建 Supabase 项目时,身份验证会自动启用。您无需在 Supabase 设置中进行任何特殊配置。虽然我们在“创始人工具包”中针对匿名用户和团队成员分别设置了一些具体的 RLS 安全策略,但对于此数据库,我们将使用 Supabase 的默认内置身份验证功能。
Supabase 会自动配置以下内容:
- 已启用电子邮件/密码认证
- 已创建用户表(auth.users)
- 已签发的 JSON Web Tokens (JWT)
- 会话管理已处理
在第一个练习中,你将为你的MVP添加登录和注册流程,以便只有注册用户才能访问你的应用。我们将按照一系列提示逐步操作,并在每个步骤后进行测试,以确保一切正常运行后再继续下一步。
本课活动中的每个提示都使用了 “角色 + 任务” 结构。这是一种值得练习的提示设计模式:
- 角色 告知AI应成为何种专家
- 任务 明确告诉它该做什么。
将这两者分开,可以使您的提示更清晰,并获得更好的结果,特别是在使用较小的AI模型时。
活动 1
使用 Supabase 添加用户身份验证
预计时间:30分钟
您将更新应用,使其在使用时需要登录。新用户可以注册,老用户可以登录,而没有账户的用户将被重定向至登录页面。
1. 在您的 GitHub 仓库中创建一个新分支
- 前往 GitHub,打开你的 MVP 代码库。
- 创建一个名为“add-auth”之类的新分支。
- 在新分支上打开您的 Codespace。
你即将进行重大更改——功能分支能确保主分支的安全。只有在测试完成后,你才会将代码合并到主分支。
2. 在 Supabase 中创建一个新项目
- 访问 Supabase。您应该已经通过“创始人工具包”拥有了一个账户和一个项目。
- 创建一个新项目。为该项目起一个适合您MVP的名称。
- 您需要设置一个数据库密码。请设置一个密码,并将其保存在安全的地方,因为保存后您将无法查看该密码。
- 配置身份验证设置:
- 进入您的Supabase 仪表盘 → 身份验证 → 设置
- 向下滚动至“登录/服务提供商”部分
- 查找“启用电子邮件确认”
- 将其切换为“关闭”(用于测试)
- 投入生产环境时,请记得将其重新启用!
- 获取您的项目 URL 和 API 密钥
- 在左侧边栏中,点击“项目设置”(底部的小齿轮图标)
- 点击“数据 API”。
- 您将看到API URL。请将其复制并保存在电脑上的某个位置。您在开发应用时会用到它。
- 返回“项目设置”菜单,然后点击“API密钥”。
- 你会看到两个密钥:“可发布密钥”和“私钥”。
- 复制可发布密钥。您的应用也需要用到它。
公共 URL 和可发布密钥均为公开状态,可在您的应用前端使用。由于 Supabase 采用了 RLS(行级安全),数据得到了有效保护。用户可以注册和登录,但只能查看自己的数据,且不具备管理员权限。
这里我们不会使用密钥。该密钥绝不能在前端使用。我们稍后在处理支付时会用到它。
3. 将 Supabase URL 和 API 密钥添加到 .env 文件中
- 在您的项目中,添加一个新的 .env 文件,将其放置在 根目录中添加一个新的.env文件。你应该已经有一个 .env 文件 server 文件夹中,但由于 Supabase 的集成是在前端进行的,因此该文件必须位于项目的根目录下。
- 在根目录下添加 2 行代码 .env 文件中,添加以下两行:
VITE_SUPABASE_URL=your_public_URL
VITE_SUPABASE_KEY=your_publishable_key - 请在该文件的相应行中填写您实际的 Supabase URL 和可发布密钥。
注意:我们使用的是 React,因此需要在前端环境变量前添加前缀 VITE_。您的框架可能有所不同。
4. 安装 Supabase 客户端
在您的 Codespace 终端中,输入:
npm install @supabase/supabase-js
5. 提示 Copilot 创建 Supabase 注册表单并处理
提示 Copilot(或其他大型语言模型)协助生成您应用的登录/注册代码。以下是一个示例提示。
角色:你是一名网页开发人员,正在帮助我将用户身份验证功能集成到我的 React 应用中。
任务:添加一个 Supabase 注册和登录表单,并与 Supabase 网址进行交互。初始界面为登录页面,用户登录后,其他标签页将显示出来,用户可访问这两个标签页。登录表单应作为 Authform.jsx 文件添加到 Pages 文件夹中。 在用户登录后,需在屏幕上其他标签页按钮旁边添加一个注销按钮,以便用户注销并返回主界面。
请为“AuthForm”采用与“FindTutor”和“GetAdvice”选项卡相同的格式设置。
我已将 Supabase 项目的 URL 和可发布的 API 密钥作为 VITE_SUPABASE_URL 和 VITE_SUPABASE_URL 添加到我的 .env 文件中。
6. 测试身份验证
测试整个流程:
- 注册:
- 访问 /signup
- 请输入电子邮箱和密码(至少6个字符)
- 点击“注册”
- 应重定向至首页
- 在 Supabase 中验证:
- 打开 Supabase 仪表盘 → 身份验证 → 用户
- 您应该能看到新用户已列入列表
- 注销:
- 点击“注销”按钮
- 应重定向至登录页面
- 登录:
- 请输入相同的电子邮箱和密码
- 应重定向至首页
- 会话持久化:
- 刷新页面
- 应该仍然处于登录状态
如果任何一步操作失败,请使用 Copilot 的“教我”模式:描述你预期的结果、实际发生的情况,并请 Copilot 先进行解释,然后再提供解决方案。
请保持代码空间和分支处于打开状态,以便进行下一项活动。
虽然添加 Google 登录选项并非强制要求,但您可以免费实现这一功能,而且这能为您的应用增添一份专业感。
活动 2
(可选)添加 Google 登录功能
预计时间:30分钟
1. 在 Supabase 中启用 Google 登录
- 转到您的Supabase 仪表盘 → 身份验证 → 提供商。
- 在列表中找到“Google”,然后将其启用。
- 找到“回调 URL”,并将其复制下来。该 URL 通常如下所示:https://[YOUR-PROJECT-ID].supabase.co/auth/v1/callback。
2. 在 Google Cloud 控制台中创建一个项目
重要提示:请忽略 屏幕顶部醒目的“免费试用”或“激活 300 美元信用额度”横幅。如果您点击这些按钮,Google会要求您提供信用卡信息以进行身份验证。
您无需300美元的信用额度或“计费账户”即可使用身份/身份验证API。
访问Google Cloud 控制台。您 应该在使用 Google AI Studio 时已拥有一个账户。
创建一个新项目,并为其命名。 Google 允许每个账户创建多个免费项目,无需付费或使用信用卡。
转到“API 和服务”→“OAuth 同意页面”。
- 点击“开始使用”。
- 请填写您的应用名称和用户支持邮箱。
- 点击 外部 面向观众。
重要提示:请仅提供“应用名称”和“支持邮箱”。如果系统要求您添加徽标或敏感的“权限范围”(例如读取用户的整个云端硬盘),Google 可能会将其标记为需要通过信用卡进行验证。
MVP 实用技巧:只要您保持在“测试”模式,或者用户数量较少,目前还无需提交审核。
3. 生成 OAuth 凭据
- 转到“API 和服务”→“凭据”。
- 点击“+ 创建凭据”→“OAuth 客户端 ID”。
- 应用程序类型:选择“Web 应用程序”。
- 授权的重定向 URI:粘贴您从 Supabase 复制的回调 URL。
- 点击“创建”。您将收到一个客户端 ID和客户端密钥。
- 请务必复制这些内容,因为关闭窗口后将无法再访问它们。您可以将其下载为 JSON 格式,这样两种格式都能供您使用。
4. 在 Supabase 中启用该提供商
- 返回Supabase 仪表盘 → 身份验证 → 提供商 → Google。
- 请粘贴您的客户端 ID 和客户端密钥。
- 将“启用 Google 提供商”切换为“开”,然后点击“保存”。
5. 在 Supabase 中将您的 GitHub Codespace URL 添加为重定向 URL
当用户使用 Google 登录时,系统需要知道认证完成后应跳转到哪里。默认跳转地址为http://localhost:3000/,但我们的前端运行在http://localhost:5173 上。
- 为确保其正常运行,请在应用运行时复制 GitHub 链接(链接形式大致如下:https://codespace_name.app.github.dev/
- 在 Supabase 中,转到“身份验证”→“URL 配置”。
- 将您的 GitHub 网址添加为重定向网址。
6. 将 URL 添加到 .env 文件中
- 在您的 .env 文件中,作为变量VITE_DEVELOPMENT_URL(仅当您的应用是 React 时才需要 VITE)。
7. 提示 Copilot 在您的应用中添加 Google 登录功能
您将要求 Copilot 添加 Google 登录功能。请注意,我们正在添加包含重定向 URL 的操作说明。
- 提示 Copilot 在您的登录界面中添加“Google 登录”作为选项。
角色:你是一名网页开发人员,正在帮助我将用户身份验证功能集成到我的 React 应用中。
任务:在我的登录页面中添加“Google 登录”选项。使用 Supabase 的 signInWithOAuth 方法,并指定“Google”提供商。重定向至环境变量 VITE_DEVELOPMENT_URL。
注意:如果您 切换代码空间,则需要创建一个包含重定向设置的新 .env 文件(同时还需包含 Supabase 的 URL 以及前端所需的 API 密钥)。
在部署应用时,您需要在 Supabase 中将生产环境的应用 URL 添加为重定向,同时在代码中也将该 URL 添加为重定向。
9. 将你的工作提交到 GitHub
- 将更改提交到你的分支。
请保持该分支和代码空间处于打开状态,以便进行下一项活动。
保护您的 API 路由
您的应用现在已拥有登录页面,因此用户必须登录才能查看您的应用。但您的 Express.js 后端仍会处理收到的每个请求,即使用户并未通过前端登录也是如此。有人可能会打开浏览器控制台,直接调用您的 API,从而完全绕过登录页面。
这就是“窗口”问题。你虽然用登录页面锁上了“前门”,但“窗口”(即你的 API 路由)却仍然敞开着。要解决这个问题,你需要添加一个 安全检查 (名为 中间件),用于验证每个 API 请求是否确实来自已登录的用户。
您需要:
保护符合以下条件的路线:
- 产生费用(AI 调用、API 使用)
- 修改数据(创建、更新、删除)
- 显示用户特定信息
保持公开:
- 健康检查
- 静态资源
- 主页(如对公众开放)
当您的后端收到请求时:
- 检查:“此请求是否附有证明用户已登录的凭据?”
- 如果是→ 按常规流程处理该请求
- 如果不存在→ 返回“401 未授权”错误并拒绝该请求
该“证明”是一个 JWT(JSON Web Token),即 Supabase 为每位已登录用户生成的长字符串。您的前端在向后端发送的每次请求中都会包含此令牌。后端会通过 Supabase 验证该令牌,以确认其真实有效且未过期。
内容包括:
- 用户是谁(其用户 ID、电子邮箱)
- 发布时
- 到期时
- 一个加密签名,用于证明该签名由 Supabase 签发且未被篡改
活动 3
保护您的 API 路由
预计时间:25分钟
1. 打开Github Codespaces
如果您尚未进入 Codespace,请打开它,并选择与活动 1 和 2 相同的分支。
2. 在后端安装 Supabase
- 在您的 服务器 文件夹(您的 Express 后端所在的位置)中,安装 Supabase:
- 在终端窗口中,输入:
,然后输入:cd server,接着输入:
,最后输入:npm install @supabase/supabase-js
- 在终端窗口中,输入:
3. 添加后端环境变量
- 返回您的 Supabase 项目,并获取 秘密 API 密钥 (不是可发布密钥)。
- 转到“项目设置”→“API密钥”
- 向下滚动至“密钥”部分。
- 将其复制并保存在电脑上的某个安全位置。
- 更新 .env 文件,位于您的 server 文件夹中更新.env文件(不是根目录下的 .env,而是 server/.env 是后端专用的独立文件)。
- 添加以下几行代码,并将您的 Supabase URL 和密钥粘贴进去:
SUPABASE_URL=您的_公开_URL
SUPABASE_SERVICE_ROLE_KEY=您的_密钥
重要提示:
- 请使用与前端相同的SUPABASE_URL
- 请将“SECRET KEY”(非公开密钥)用于SUPABASE_SERVICE_ROLE_KEY
- 密钥具有管理员权限——切勿在前端代码中暴露该密钥
4. 在 Express 后端中添加身份验证中间件
- 使用此提示语,让 Copilot 在后端代码中添加代码,以确保 API 调用安全。
角色:你是一名后端开发人员,负责协助我加强 Express.js API 的安全性。
任务:
- 导入 Supabase 客户端库 (@supabase/supabase-js)
- 使用环境变量中的 SUPABASE_URL 和 SUPABASE_SERVICE_ROLE_KEY 初始化一个 Supabase 客户端。
- 在我的 Express.js 后端中添加一个身份验证中间件函数。
- 提取令牌
- 请求 Supabase 验证该令牌
- 如果是 → 允许请求继续
- 如果为空 → 返回 401 错误
仅将此中间件应用于调用外部 API 的路由
请勿将其应用于首页或健康检查等公共路径
- 查看生成的代码。
你会看到一个名为requireAuth(或类似名称)的函数,该函数用于检查 Authorization 头,提取令牌,并通过 Supabase 进行验证。你还会发现该函数已应用于你的 API 路由,但未应用于公共路由。
关键部分应与以下代码类似:
async function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
const { data, error } = await supabase.auth.getUser(token);
if (error || !data.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.user = data.user;
next();
}
// Protected route — security check runs first
app.post('/api/ai-query', requireAuth, async (req, res) => {
// This code only runs if the user is logged in
});
// Public route — no security check needed
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
对于 TutorMatch 而言,受保护的路由是app.post('/api/ai-query', requireAuth, …)。您的应用程序可能会使用不同的路由名称,但模式是一样的——对运行会产生成本的路由进行保护。
5. 更新前端以发送令牌
您的后端现在要求每次请求都提供登录凭证,但您的前端尚未发送该凭证。
- 提示 Copilot 更新您的前端代码。以下是一个示例提示。
角色:你是一名前端开发人员,负责帮助我将 React 应用与我的安全 Express.js 后端进行连接。
任务:将我所有的前端 API 调用更新为受保护的路由,并在 Authorization 头部中包含 Supabase 会话令牌。
- 请查看 Copilot 更新的代码。
你会发现,你的fetch调用现在包含了 Authorization 头部。以下是修改后的代码:
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
window.location.href = '/login';
return;
}
const response = await fetch('/api/ai-query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({ prompt: userInput })
});
这里就是一切的交汇点:
- 当用户登录时,Supabase Auth 会向前端返回一个令牌。
- 您的前端会在每次请求中发送该令牌。
- 您的后端会进行检查。
- 如果请求有效,则该请求将被处理。
- 如果不是,那就说明被屏蔽了。
6. 测试两层安全措施
- 在 Codespaces 中运行您的应用,并测试以下两种场景:
测试 1 — 已登录用户(应能正常工作):
- 打开您的应用,并使用测试凭据登录。
- 请照常使用您的AI功能。
- 它应该和以前一样正常工作。
如果这能正常工作,说明你的前端正在发送令牌,而你的后端正在接受该令牌。
测试 2 — 未登录(应被阻止):
- 从应用中注销,以便用户处于未登录状态。
- 打开开发者工具(按 F12 或右键单击“检查”),然后单击“控制台”选项卡。
- 请尝试将以下内容粘贴到控制台中,直接调用您的 API(注意:我们的 fetch 命令是 /api/ai-query——请将您的 fetch 命令修改为与前端代码中一致的命令。)
fetch('/api/ai-query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: 'test' })
}).then(r => r.json()).then(console.log);
You should see { error: “Unauthorized” } — NOT an AI response.
如果这两项测试都通过,说明您的应用是安全的。登录页面保护着“大门”,中间件则保护着“窗户”。
7. 提交和合并
请最后再检查一次,确保用户身份验证正常:
- 请完全退出登录并刷新页面——此时您应该会看到登录页面
- 登录并验证您的 AI 功能是否正常工作
- 打开开发者工具控制台,尝试在不使用令牌的情况下调用您的 API(如测试 2 所示)——确认是否收到 401 错误
在源代码控制系统中将更改进行暂存并提交,提交信息可写为“添加基于身份验证中间件的 API 路由保护”,然后将代码推送到你的分支。
前往 GitHub,从你的add-auth 分支 创建一个 Pull Request,审核更改,然后合并到主分支。
故障排除
以下是一些可能出现的问题及相应的解决方案
已登录用户收到 401 错误
打开开发者工具,切换到“网络”选项卡,点击该 API 请求,然后查看“请求头”。您应该会看到Authorization: Bearer eyJ…这一条。如果该请求头缺失,请确认您的getSession()调用是否正常。
API调用无需登录即可进行
请确认您已将requireAuth应用到了正确的路由上。
代币一直被拒绝
请确保在后端使用的是SUPABASE_SERVICE_ROLE_KEY(而非SUPABASE_ANON_KEY)。该服务角色密钥具有验证任何用户令牌的权限。
“supabase 未定义”的提示信息
请确认已安装 Supabase 客户端库(npm install @supabase/supabase-js),并在服务器文件的开头导入该库。
反思
添加用户身份验证是让您的应用更专业、更安全的重要一步。请考虑以下问题:
安全
您的 API 路由
提示
关键术语
- 身份验证:验证用户的身份——确认其确为自称的身份(注册和登录)。这与授权不同,授权用于控制用户可以访问的内容。
- 授权:控制经过身份验证的用户被允许执行哪些操作。行级安全(RLS)是在数据库层实现此功能的一种方式。
- 密码哈希:在存储密码之前,将其转换为不可读的字符串。Supabase Auth 会自动执行此操作。存储明文密码会带来严重的安全风险。
- 会话令牌:一种临时标识符,用于在用户通过身份验证后保持其登录状态。Supabase Auth 会自动管理这些令牌。
- 身份验证中间件:在路由处理程序之前运行的代码,用于验证请求是否来自已登录的用户。如果用户未通过身份验证,该中间件将阻止该请求。
- 授权头:一种HTTP头,用于在每次请求中将身份验证凭据(通常为令牌)从前端传递到后端。
- Bearer Token: A type of authentication token sent in the Authorization header. The format is Bearer <token>. “Bearer” means “whoever carries this token is authorized.”
- JSON Web Token(JWT):一种身份验证令牌的标准格式。其中包含关于用户的编码信息以及过期时间。Supabase 使用 JWT 进行会话管理。
- OAuth:一种身份验证 标准,允许用户使用现有账户(如 Google)登录,而无需创建新密码。是“开放授权”(Open Authorization)的缩写。
其他资源
- Supabase 身份验证文档:设置指南、提供商配置和 API 参考
- 适用于 Express 的 Supabase 身份验证辅助函数:服务器端身份验证模式
