让用户登录:给网站加上身份认证
现在任何人都能访问你的 /admin 后台。这不安全。
这一关加上用户登录,让后台只有你能进。
为什么需要用户认证?
你的后台管理页面(/admin/works)目前是完全公开的——任何知道这个 URL 的人都能访问,修改你的作品数据。
用户认证(Authentication)解决的就是这个问题:验证"你是你"。
认证系统的核心流程:
用户输入邮箱+密码 ↓ 服务器验证是否匹配 ↓ 匹配成功 → 生成 Session Token(一串随机字符) ↓ Token 存入 Cookie ↓ 之后每次请求,浏览器自动带上这个 Cookie ↓ 服务器验证 Cookie 里的 Token → 确认用户身份
认证 vs 授权
这两个概念经常被混淆:
认证(Authentication):你是谁?验证身份。
- 例:输入密码登录
授权(Authorization):你能做什么?验证权限。
- 例:普通用户只能看,管理员才能改
这一关主要做认证——确认只有你(管理员)能进后台。
为什么用 Supabase Auth?
你已经在用 Supabase 做数据库了。Supabase 内置了完整的认证系统,不需要额外安装服务:
- 支持邮箱/密码登录
- 支持 Google、GitHub 等第三方登录(OAuth)
- 自动处理 Token 的生成、存储、刷新
- 与数据库的行级安全(RLS)深度集成
自己从头实现一套认证系统需要处理:密码哈希、Token 生成、Session 管理、CSRF 防护……用 Supabase Auth 可以跳过所有这些,直接用现成的。
理解 Middleware
这一关会用到 Next.js 的 Middleware(中间件)。
Middleware 是在请求到达页面之前运行的代码。你可以在这里:
- 检查用户是否已登录
- 如果没登录,重定向到登录页
- 如果已登录,放行
用户访问 /admin/works ↓ Middleware 运行(检查 Cookie 里有没有有效的 Session) ↓ 有 Session → 放行,显示页面 没有 Session → 重定向到 /admin/login
Middleware 的好处是:你只需要写一次,所有 /admin/* 路由都自动受保护,不需要在每个页面里单独检查。
第一步:开启 Supabase 认证
Supabase 内置了完整的用户认证系统,不需要自己写。
- 打开 Supabase 项目,点击左侧 "Authentication"
- 点击 "Providers",确认 "Email" 已开启
- 点击 "Users",点击 "Invite user",输入你的邮箱,发送邀请
- 去邮箱点击邀请链接,设置密码
💡 为什么用邀请而不是注册? 因为你不想让任何人都能注册账号。邀请方式只有你自己有账号,更安全。
第二步:做登录页面
对 AI 说:
帮我给网站加上用户登录功能: 1. 安装 @supabase/ssr:npm install @supabase/ssr 2. 创建登录页面 /admin/login: - 简洁的居中卡片布局 - 邮箱和密码输入框 - 登录按钮 - 登录失败时显示错误提示("邮箱或密码错误") - 登录中时按钮显示 loading 状态 3. 登录成功后跳转到 /admin/works 4. 创建 app/api/auth/login/route.ts 处理登录请求 参考 Supabase 官方的 Next.js Auth 文档实现。
✅ 马上验证: 访问
/admin/login,用你的邮箱和密码登录,确认能跳转到/admin/works。确认可用后再继续下一步。
第三步:保护后台路由
对 AI 说:
保护所有 /admin 路由,未登录时自动跳转到登录页: 1. 创建 middleware.ts(在项目根目录,不是 app 目录里) 2. 检查用户是否已登录(通过 Supabase session) 3. 访问 /admin/* 路径时,如果未登录,重定向到 /admin/login 4. 已登录时访问 /admin/login,重定向到 /admin/works 5. 在后台页面的右上角加上"退出登录"按钮,点击后清除登录状态并跳转到首页 middleware.ts 的大致结构: import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' export async function middleware(req) { const res = NextResponse.next() const supabase = createMiddlewareClient({ req, res }) const { data: { session } } = await supabase.auth.getSession() if (!session && req.nextUrl.pathname.startsWith('/admin')) { return NextResponse.redirect(new URL('/admin/login', req.url)) } return res }
退出登录后,尝试直接访问 /admin/works,确认会被重定向到登录页。
第四步:优化登录体验(可选)
基础功能做好后,可以进一步优化:
优化登录页面的体验: 1. 添加"记住我"选项(延长 session 有效期) 2. 支持按 Enter 键提交表单 3. 密码输入框右侧加"显示/隐藏密码"按钮 4. 登录失败时,密码框自动清空并获得焦点 5. 页面标题改为"管理员登录 - [你的网站名]"
安全注意事项
不要在前端存储密码:密码只在登录时发送给服务器,之后只存 Token。
Session 有效期:Supabase 默认 Session 有效期是 1 小时,之后自动刷新(只要用户还在活跃)。
HTTPS 是必须的:Cookie 里的 Token 在 HTTP 下可能被截获。Vercel 部署默认是 HTTPS,本地开发用 localhost 也是安全的。
不要把 Service Role Key 暴露给前端:Supabase 有两种 Key:anon key(可以暴露)和 service_role key(绝对不能暴露,有完全的数据库权限)。
通关条件
完成以下三步即可通关:
- 登录页面可以正常访问(
/admin/login) - 用你的邮箱和密码可以成功登录,跳转到后台
- 未登录时访问
/admin/works会自动跳转到登录页
卡住了?
登录后还是跳回登录页? 让 AI 检查 middleware.ts 里的 session 读取逻辑,确认 createMiddlewareClient 的用法正确。
邮件没收到? 检查垃圾邮件文件夹,或者在 Supabase Authentication → Users 里直接修改密码(点击用户 → Reset Password)。
退出登录不生效? 让 AI 确认退出时调用了 supabase.auth.signOut(),并且清除了 Cookie。
Middleware 不生效? 确认 middleware.ts 在项目根目录(和 package.json 同级),不是在 app/ 目录里。
通关条件
完成以下三步即可通关:
- 登录页面可以正常访问(
/admin/login) - 用你的邮箱和密码可以成功登录,跳转到后台
- 未登录时访问
/admin/works会自动跳转到登录页
卡住了?
登录后还是跳回登录页? 让 AI 检查 middleware.ts 里的 session 读取逻辑,确认 createMiddlewareClient 的用法正确。
邮件没收到? 检查垃圾邮件文件夹,或者在 Supabase Authentication → Users 里直接修改密码(点击用户 → Reset Password)。
退出登录不生效? 让 AI 确认退出时调用了 supabase.auth.signOut(),并且清除了 Cookie。
Middleware 不生效? 确认 middleware.ts 在项目根目录(和 package.json 同级),不是在 app/ 目录里。