核心概念
| 概念 | 层级 | 作用 |
|---|---|---|
| Auth Guard | 前端 | 路由保护,未登录重定向 |
| tRPC Middleware | 后端 | API保护,校验用户身份 |
| Context | 共享 | 请求级共享数据容器 |
请求 → Auth Guard(前端) → tRPC Middleware(后端) → Procedure
↓ ↓
页面重定向 返回 401 错误
Auth Guard(前端路由守卫)
作用
保护 Next.js 受保护路由(如 /dashboard),未登录用户重定向到登录页。
实现方式
Server Component 方式(推荐):
// app/dashboard/page.tsx
import { auth } from '~/server/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/auth/login')
}
return <div>Protected content</div>
}
Middleware 方式(全局路由保护):
// middleware.ts
import { type NextRequest, NextResponse } from "next/server";
import { auth } from "~/server/auth";
export async function middleware(request: NextRequest) {
const session = await auth();
if (!session && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/auth/login", request.url));
}
return NextResponse.next();
}
特点
- 防君子:仅阻止普通用户直接访问
- 用户体验:避免看到 401 错误页面
- 可绕过:开发者工具或直接调用 API 可绕过
tRPC Middleware(后端 API 保护)
作用
保护 tRPC API 端点,验证用户 session 有效性,未登录返回 UNAUTHORIZED 错误。
实现机制
Context(上下文):
// src/server/api/trpc.ts
export const createTRPCContext = async (opts: { headers: Headers }) => {
const session = await auth();
return { db, session };
};
Middleware(中间件):
const timingMiddleware = t.middleware(async ({ next, path }) => {
const start = Date.now();
const result = await next();
console.log(`[TRPC] ${path} took ${Date.now() - start}ms`);
return result;
});
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({ ctx: { session: { ...ctx.session, user: ctx.session.user } } });
});
Procedure 定义:
export const publicProcedure = t.procedure.use(timingMiddleware);
export const protectedProcedure = t.procedure
.use(timingMiddleware)
.use(isAuthed);
使用示例
// src/server/api/routers/resume.ts
export const resumeRouter = createTRPCRouter({
// 公开接口(可访问但无用户信息)
getBySubdomain: publicProcedure.query(...),
// 受保护接口(必须登录)
createResume: protectedProcedure.mutation(...),
getMyResumes: protectedProcedure.query(...),
})
特点
- 防小人:即使绕过前端,API 也会校验
- 安全:session token 验证在后端完成
- 职责链:可串联多个 middleware(日志、计时、权限)
哲学对比
设计模式相同
两者都基于责任链模式(Chain of Responsibility):
- 每个组件只做一件事
- 处理完选择放行或拦截
- 链式串联,职责清晰
Java Filter vs tRPC Middleware
| Java | tRPC |
|---|---|
| Filter | Middleware |
| FilterChain | Procedure chain |
| doFilter() | next() |
| @WebFilter | tRPC middleware |
前端 Guard vs 后端 Middleware
| 维度 | Auth Guard | tRPC Middleware |
|---|---|---|
| 位置 | 前端/路由层 | 后端 API 层 |
| 触发 | 页面导航 | API 请求 |
| 绕过难度 | 易(DevTools) | 难(需伪造 token) |
| 响应 | 页面重定向 | 401 错误 |
| 作用 | 体验优化 | 安全防护 |
最佳实践
双重保护
// 前端 Guard(体验)
if (!session) redirect("/auth/login");
// 后端 Middleware(安全)
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session?.user) throw new TRPCError({ code: "UNAUTHORIZED" });
return next();
});
Context 缓存
// RSC 中使用 cache 避免重复创建
const createContext = cache(async () => {
return createTRPCContext({ headers });
});
错误处理
// 统一错误格式化
errorFormatter({ shape, error }) {
return {
...shape,
data: { ...shape.data, zodError: error.cause instanceof ZodError ? ... : null }
}
}
常见问题
Q: 为什么需要双重保护?
前端 Guard 改善用户体验,后端 Middleware 保证安全。两者各司其职,缺一不可。
Q: session 每次请求都查库?
auth()实现决定。如果有缓存则无性能问题。
Q: middleware 中 throw 错误和 redirect 如何选择?
API 层 throw 401,页面层 redirect。职责分明。