8.3 路由保护与权限控制
本节目标:理解为什么前端隐藏入口不等于安全,学会用 Middleware 和权限控制守住每一个路由。
前端隐藏不是安全
小明给"个人豆瓣"加了管理后台 /admin,可以批量删除电影、管理用户评论。他在前端导航栏里没有放 /admin 的入口,觉得这样就安全了——普通用户看不到入口,自然就进不去。
直到有一天,朋友老王在群里说:"你这个管理后台挺好用的,我帮你删了几条重复的电影。"
小明吓了一跳:"你怎么进去的?"
老王:"我在浏览器地址栏直接输了 localhost:3000/admin,就进去了啊。"
小明这才明白:前端隐藏入口只是掩耳盗铃。不在导航栏放链接,只是让用户"看不到"入口,但任何人都可以在地址栏手动输入 URL。更进一步,攻击者可以用 curl 发请求、写脚本批量调用你的 API——前端的"隐藏"在这些方式面前形同虚设。这就像你把家门钥匙藏在花盆下面,然后把花盆用布盖住。你觉得别人看不到花盆就找不到钥匙,但任何人只要掀开布就能拿到。真正的保护必须在服务端完成——不是"藏起入口",而是"没有钥匙就打不开门"。
Middleware——Next.js 的守门员
在 Next.js 中,你不需要在每个页面都写权限判断。只需要在项目根目录放一个 middleware.ts 文件——它就像网站的守门员,每一个请求到达页面或 API 之前,都要先经过它的检查。用户请求 /admin,Middleware 拦截,检查是否登录、是否是管理员。未登录就重定向到 /login,不是管理员就返回 403——403 的意思是"我知道你是谁,但你没权限",和 401"你是谁?先登录"不同——通过了才放行到 /admin 页面。这个检查发生在服务端,用户的浏览器根本不会收到 /admin 页面的内容——不是"页面加载了再跳转",而是"页面压根没发给你"。
跟 AI 说:"创建一个 middleware.ts,拦截所有以 /admin 开头的路径。如果用户没有登录或者角色不是 admin,重定向到登录页。同时保护所有 /api/admin 开头的接口。"
好奇 Middleware 长什么样?展开看看
// middleware.ts(项目根目录)
import { betterFetch } from '@better-fetch/fetch'
import { NextRequest, NextResponse } from 'next/server'
export async function middleware(request: NextRequest) {
// 检查用户是否登录
const { data: session } = await betterFetch('/api/auth/get-session', {
baseURL: request.nextUrl.origin,
headers: { cookie: request.headers.get('cookie') || '' },
})
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
// 指定哪些路径需要保护
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
}Middleware 不只能保护页面,也能保护 API。通过 matcher 配置,你可以精确控制哪些路径需要检查——/dashboard/:path* 保护所有 dashboard 页面,/admin/:path* 保护管理后台,/api/admin/:path* 保护管理 API。:path* 是通配符,意思是"这个路径下的所有子路径",所以 /admin/users、/admin/settings 都会被保护。不需要保护的路径(首页、登录页、注册页、公开 API)不写在 matcher 里就行——Middleware 只拦截你指定的路径,其他路径直接放行。
页面保护和接口保护缺一不可
小明加了 Middleware,保护了 /admin 页面。他觉得安全了——用户在浏览器里访问 /admin 会被拦截。但老师傅问了一个问题:"你的删除电影接口 /api/admin/delete-movie 也保护了吗?"
小明说:"Middleware 不是已经拦了 /admin 开头的路径吗?"
老师傅说:"Middleware 拦的是页面请求。但如果有人直接用 curl 调用 POST /api/admin/delete-movie,带上一个合法用户的 Cookie,Middleware 可能拦不住——取决于你的 matcher 配置。而且即使 Middleware 拦了,API 内部也应该有自己的权限检查,这是纵深防御的原则。"
为什么两层都要?因为 Middleware 是一道"大门",但大门后面的每个房间也应该有自己的锁。即使页面被保护了,攻击者仍然可以绕过浏览器,直接用 curl 或 Postman 调用 API 接口——就像有人不走商场正门,而是从员工通道直接进了仓库,你在正门设的安检对他完全无效。如果 API 没有自己的权限检查,攻击者只要拿到一个合法用户的 Cookie,就能用 curl 删除数据、修改配置、甚至提升权限。页面级保护靠 Middleware 拦截加服务端检查,接口级保护靠 API Route 内部校验——两层都在服务端控制,都无法被绕过。这就是安全领域常说的纵深防御:不把所有鸡蛋放在一个篮子里,每一层都有自己的防护。
跟 AI 说:"确保所有 /api/admin 开头的接口都在代码内部检查用户权限,不要只依赖 Middleware。"
CORS——浏览器的跨域安全机制
小明后来想给"个人豆瓣"加一个功能:调用豆瓣的公开 API 获取电影海报。他在前端写了一个 fetch('https://api.douban.com/...'),浏览器控制台立刻报了一个红色错误:"跨域请求被阻止(CORS policy)"。他很困惑——这个 API 在浏览器地址栏里直接打开是能看到数据的,为什么用 JavaScript 调用就不行?
这不是 bug,是浏览器的安全机制。想象一下:你登录了银行网站,浏览器里存着你的登录 Cookie。然后你打开了一个恶意网站。如果没有跨域限制,这个恶意网站的 JavaScript 可以偷偷向银行网站发请求——因为浏览器会自动带上银行的 Cookie,银行服务器以为是你本人在操作。攻击者就能冒充你转账。CORS(跨域资源共享)就是浏览器的防线:默认禁止网页里的 JavaScript 向其他域名发请求,除非对方的服务器明确允许。在浏览器地址栏直接访问不受限制(那是你主动访问),但网页里的 JavaScript 代码发请求会被拦截(因为可能是恶意代码在偷偷发)。
什么时候会遇到 CORS?如果前后端在同一个 Next.js 项目内(本教程的情况),前端调自己的 API Route,不会遇到 CORS 问题,因为是同源的。但如果前后端分离部署——前端在 app.example.com,API 在 api.example.com——就会遇到。从前端直接调用第三方 API 也会遇到。如果你的前后端在同一个 Next.js 项目里,通常不需要配置 CORS。如果确实需要跨域,告诉 AI:"我的前端在 https://app.example.com,需要调用 https://api.example.com 的接口,帮我配置 CORS,只允许这个域名访问。"
不要配置 Access-Control-Allow-Origin: *
允许所有域名访问等于没有 CORS 保护。始终指定具体的域名。
角色权限控制
Middleware 解决了"有没有登录"的问题,但小明很快遇到了新问题:老王登录后,居然也能访问 /admin 页面——因为 Middleware 只检查了"是否登录",没检查"是不是管理员"。小明的应用有两种用户:他自己是管理员,能删除电影、管理评论;朋友们是普通用户,只能看电影、写评论。这就需要基于角色的访问控制(RBAC)——不同角色能做不同的事。
最简单的实现是在 user 表里加一个 role 字段:小明的 role 是 admin,老王的 role 是 user。然后在 Middleware 或 API 里检查 session.user.role !== 'admin' 就返回 403。这个检查可以放在 Middleware 里(统一拦截),也可以放在每个 API Route 里(精细控制),最好两个地方都放——又是纵深防御。
随着应用复杂度增长,简单的 admin/user 两个角色可能不够。比如小明的"个人豆瓣"火了,有了几百个用户,他需要几个信任的朋友帮忙管理评论区。这时候就需要更细的角色划分:guest 只能浏览公开内容,user 可以浏览、发布、编辑自己的内容,moderator 拥有 user 的所有权限加上删除违规内容的权力,admin 拥有所有权限。Better Auth 的组织管理插件支持更复杂的权限模型,但对于大多数个人项目,admin/user 两个角色就够了。不要过度设计——等真正需要时再扩展,到时候告诉 AI 你的需求,它会帮你调整权限模型。
跟 AI 说:"帮我实现完整的路由保护方案:创建 middleware.ts 保护 /dashboard 和 /admin 路径,/admin 路径额外检查用户角色必须是 admin,所有 /api/admin 开头的接口也要在代码内部检查权限,未登录用户重定向到 /login,登录用户访问 /login 时重定向到 /dashboard。"
本节核心要点
- 前端隐藏入口不是安全措施,真正的保护在服务端
- Middleware 是 Next.js 的统一拦截层,用
matcher指定保护范围 - 页面保护和接口保护缺一不可
- CORS 是浏览器的安全机制,同项目内通常不需要配置
- 角色权限从简单的 admin/user 开始,需要时再扩展
下一步
路由守住了,接下来去 安全检查与问题排查——一份开发各阶段的安全清单,以及遇到问题时的快速排查手册。
