前端转全栈 07:认证与授权——Supabase Auth、GitHub OAuth 与 RLS
系列目录:本文是「前端转全栈:Next.js + Supabase 实战」系列的第 7 篇。前面接口都假设「用户已登录」,这一篇真正把用户系统做出来。
「认证」和「授权」常被混为一谈,先厘清:
- 认证(Authentication, AuthN):你是谁?→ 登录。
- 授权(Authorization, AuthZ):你能干啥?→ 权限。
漏掉任一个都出大事:只认证不授权 → 普通用户能删别人数据;只授权不认证 → 根本不知道是谁在操作。
一、Supabase Auth:开箱即用的用户系统
本站用 Supabase Auth,你不用自己建用户表、不用写密码哈希(密码安全是地狱级难题,千万别自己造轮子)。它自带邮箱、GitHub、Google 等登录方式。
GitHub 登录只需前端一行触发:
// 浏览器端
const supabase = createClient() // src/lib/supabase/client.ts
await supabase.auth.signInWithOAuth({
provider: "github",
options: { redirectTo: `${location.origin}/auth/callback` },
})
二、本站真实的登录回调
OAuth 会跳回 redirectTo 指定的地址,并带上 code。本站 src/app/auth/callback/route.ts 负责「用 code 换会话」:
// src/app/auth/callback/route.ts
import { NextResponse } from "next/server"
import { createClient } from "@/lib/supabase/server"
export async function GET(request: NextRequest) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get("code")
const next = searchParams.get("next") ?? "/"
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) return NextResponse.redirect(`${origin}${next}`)
}
return NextResponse.redirect(`${origin}/?error=auth_error`)
}
流程:GitHub 给 code → 服务端 exchangeCodeForSession 换成会话 token 并写入 cookie → 后续请求服务端自动识别用户(呼应第 2 篇的 cookie 机制)。
这就是第 4 篇
route.ts里supabase.auth.getUser()能拿到用户的原因——token 在 cookie 里,服务端客户端都读得到。
三、会话 vs JWT(概念扫盲)
- Session(本站方案):服务端存会话,浏览器只持一个随机 ID(cookie)。好处是「可随时吊销」,坏处是要服务端存储。
- JWT:服务端签一张「带签名的通行证」发给浏览器,之后每次请求自带,服务端不存。好处是无状态、适合分布式;坏处是签发后难即时吊销。
Supabase 用的是 JWT 形式的 access token,但由它托管刷新逻辑,你基本无感。初学直接用 Supabase Auth 即可,别自己实现 JWT。
四、RLS:在数据层锁死权限(最关键的防线)
光在接口里写 if (!user) return 401 不够——万一哪天忘了写,数据库就裸奔。RLS(Row Level Security,行级安全) 把权限直接焊死在数据层:即使绕过接口直连,规则依然生效。
本站 supabase/schema.sql 的策略:
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
-- 任何人都能读
CREATE POLICY "Anyone can read comments"
ON comments FOR SELECT USING (true);
-- 只有登录用户能插入
CREATE POLICY "Authenticated users can insert comments"
ON comments FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
-- 只能删自己的
CREATE POLICY "Users can delete their own comments"
ON comments FOR DELETE
USING (auth.uid() = user_id);
auth.uid() 是 Supabase 提供的函数,返回当前登录用户 ID。USING 控制「能读/删哪些行」,WITH CHECK 控制「能插入/改哪些行」。
黄金法则:接口做校验是体验层,RLS 是安全层,两者都要有。接口挂了 RLS 还在,RLS 是最后一道不可绕过的墙。
五、别忘了「注销」
await supabase.auth.signOut() // 清掉 cookie 会话
很多新手只做登录不做注销,导致「换个用户」还是旧身份。
总结
- 认证 ≠ 授权,两者缺一不可。
- Supabase Auth 直接给登录 + 会话,别自己写密码哈希。
- OAuth 回调用
code换会话,会话存 cookie。 - RLS 是数据层最后防线,用
USING/WITH CHECK把权限焊死。 - 接口校验 + RLS 双保险。
下一篇,我们把「提交表单」这最常见的交互做成服务端能力——表单与服务端 Action。
练习:在 Supabase 后台开启 GitHub 登录(填好 GitHub OAuth App 的 Client ID/Secret),本地跑通
signInWithOAuth→ 回调 →getUser()拿到用户,体验完整的「你是谁」闭环。
Comments
Sign in to leave a comment.