RedwoodJS 认证实战:使用 dbAuth 与 @requireAuth 保护你的博客后台
后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载本篇技术指南基于 Redwood 官方教程第 4 章Authentication展开完整演示如何在 Redwood 应用中把博客管理后台迁移到/admin路径、通过内置的 dbAuth自托管认证实现登录注册并利用Private路由组件、requireAuth/skipAuth指令、useAuthHook 与SESSION_SECRET会话密钥来精细化控制页面与 GraphQL 数据层的访问权限。学完本文你将掌握 Redwood 默认安全secure by default的认证体系能独立为任意 Redwood 应用接入登录、注销、找回密码与用户信息展示。准备后台把管理页面迁移到/admin教程中博客应用的四个后台路由新建、编辑、详情、列表最初都以/posts开头普通访客和博主混在同一个 URL 空间里并不合理。把管理界面统一放到/admin前缀下是常见且合理的组织方式。由于路由的name不变所有由 scaffold 生成、基于命名路由named routes的Link都无需改动——这是 Redwood 命名路由设计带来的红利。以 TypeScript 项目为例修改 web/src/Routes.tsxJavaScript 项目为web/src/Routes.jsimport { Router, Route, Set } from redwoodjs/router import ScaffoldLayout from src/layouts/ScaffoldLayout import BlogLayout from src/layouts/BlogLayout const Routes () { return ( Router Set wrap{ScaffoldLayout} titlePosts titleToposts buttonLabelNew Post buttonTonewPost Route path/admin/posts/new page{PostNewPostPage} namenewPost / Route path/admin/posts/{id:Int}/edit page{PostEditPostPage} nameeditPost / Route path/admin/posts/{id:Int} page{PostPostPage} namepost / Route path/admin/posts page{PostPostsPage} nameposts / /Set Set wrap{BlogLayout} Route path/article/{id:Int} page{ArticlePage} namearticle / Route path/contact page{ContactPage} namecontact / Route path/about page{AboutPage} nameabout / Route path/ page{HomePage} namehome / /Set Route notfound page{NotFoundPage} / /Router ) } export default Routes访问http://localhost:8910/admin/postsscaffold 生成的页面即可正常渲染。但路径改变只解决组织问题不解决安全问题——任何人都能直接输入这个 URL 篡改博客内容。这正是接下来引入认证的原因。认证Authentication与授权AuthorizationRedwood 官方对这两个高频混淆概念给出清晰界定认证Authentication确认你是谁通常通过邮箱 密码登录或借助 Auth0 等第三方身份提供商完成。授权Authorization在用户已经认证的前提下判断你是否被允许做某件事一般涉及角色roles与权限检查在访问某个 URL 或功能之前进行。本章教程只聚焦认证授权的完整实现RBAC基于角色的访问控制见 教程第 7 章。Redwood 开箱即用提供两条认证路径自托管Self-hosted用户凭证存储在你自己数据库里也就是本教程使用的dbAuth无需任何第三方注册上手最快。第三方托管Third-party hosted用户凭证由第三方保管Redwood 提供多种集成详见 认证文档包括 Auth0、Clerk、Netlify Identity、Netlify GoTrue-JS、Magic、Nhost、Firebase 的 GoogleAuthProvider、Supabase、SuperTokens、WalletConnect 等。无论哪条路径最终你都得到一个已认证用户对象可在应用的 web 端和 api 端同时访问。安装 dbAuth 后端Redwood 用生成器generator完成认证的大部分样板工作一条命令安装 dbAuth 所需的后端组件另一条命令生成登录、注册、忘记密码页面。在项目根目录执行yarn rw setup auth dbAuth过程中会询问两件事是否覆盖已有文件/api/src/lib/auth.{js,ts}——选择yes。新应用默认生成的这个文件只是空壳用于让requireAuth等指令可运行现在要用真实的实现替换它。是否启用 WebAuthn 支持——本教程选择noWebAuthn 是独立的功能模块教程用不到。命令执行后会创建若干文件并在终端打印一些收尾说明下一步就是按其提示完成最后的定制。创建 User 模型dbAuth 依赖一个用户数据模型。博客应用目前还没有User模型需要连同 dbAuth 必需的字段一起创建。打开api/db/schema.prisma加入以下模型datasource db { provider sqlite url env(DATABASE_URL) } generator client { provider prisma-client-js binaryTargets native } model Post { id Int id default(autoincrement()) title String body String createdAt DateTime default(now()) } model Contact { id Int id default(autoincrement()) name String email String message String createdAt DateTime default(now()) } model User { id Int id default(autoincrement()) name String? email String unique hashedPassword String salt String resetToken String? resetTokenExpiresAt DateTime? }其中id、name、email是业务字段email用unique约束保证唯一另外四个字段完全由 dbAuth 接管hashedPassword用户密码与salt组合后经哈希算法处理的结果数据库中绝不明文存密码。salt一段与密码混合的随机唯一字符串用于抵御彩虹表攻击rainbow table attacks。resetToken用户忘记密码时dbAuth 会在该字段写入一个 token用户回来重置密码时必须携带它。resetTokenExpiresAtresetToken的过期时间戳超过该时间后 token 失效用户需要重新走一遍忘记密码表单。创建好模型后迁移数据库迁移名称建议为 create useryarn rw prisma migrate dev至此数据库部分全部完成。从仓库中的 dbAuth 安装模板 auth.ts.template 可以看到dbAuth 生成的getCurrentUser默认实现也正是以session.id去查db.user只返回id字段——这与教程的模型约定完全对应。私有路由用Private锁住后台页面在 api 端dbAuth 安装时会在 SDL 层注入认证校验。以posts为例重新加载/admin/posts时因为requireAuth指令的存在未登录用户发起的 GraphQL 请求会被拒绝数据不会返回——后台数据已经半个安全了。但更理想的做法是未登录用户根本看不到后台页面本身而不是看到页面骨架却拿不到数据。Redwood 路由层提供了Private组件当前实现位于 router.tsx源码中要求必须指定字符串类型的unauthenticated属性否则会抛错提示 You must specify anunauthenticatedroute when using PrivateSet。用法是把需要登录才能访问的路由包进Private并告诉它未认证用户该跳去哪条命名路由import { Private, Router, Route, Set } from redwoodjs/router import ScaffoldLayout from src/layouts/ScaffoldLayout import BlogLayout from src/layouts/BlogLayout const Routes () { return ( Router Private unauthenticatedhome Set wrap{ScaffoldLayout} titlePosts titleToposts buttonLabelNew Post buttonTonewPost Route path/admin/posts/new page{PostNewPostPage} namenewPost / Route path/admin/posts/{id:Int}/edit page{PostEditPostPage} nameeditPost / Route path/admin/posts/{id:Int} page{PostPostPage} namepost / Route path/admin/posts page{PostPostsPage} nameposts / /Set /Private Set wrap{BlogLayout} Route path/article/{id:Int} page{ArticlePage} namearticle / Route path/contact page{ContactPage} namecontact / Route path/about page{AboutPage} nameabout / Route path/ page{HomePage} namehome / /Set Route notfound page{NotFoundPage} / /Router ) } export default Routes这里unauthenticatedhome表示未登录访问被拒后跳转到首页。借力 skipAuth重新放开公共数据加完Private后再访问/admin/posts后台页面确实进不去了——但连首页的博客文章也看不到了。原因在于首页和后台共用同一个posts查询它在 posts.sdl.ts 中默认带有requireAuth全站所有使用该查询的地方都被锁死。而我们的真实需求是未登录用户也能浏览首页文章。既然后台页面已经被Private保护就可以放心地把只读查询改为skipAuthexport const schema gql type Post { id: Int! title: String! body: String! createdAt: DateTime! } type Query { posts: [Post!]! skipAuth post(id: Int!): Post skipAuth } input CreatePostInput { title: String! body: String! } input UpdatePostInput { title: String body: String } type Mutation { createPost(input: CreatePostInput!): Post! requireAuth updatePost(id: Int!, input: UpdatePostInput!): Post! requireAuth deletePost(id: Int!): Post! requireAuth } 注意细节首页列表用posts查询而点击文章后的详情页用的是单条post查询——只放开posts仍会看到详情页报错必须把post也一并skipAuth。从仓库源码可以看到这两个指令的底层实现。requireAuth在 requireAuth.ts 中通过createValidatorDirective创建支持roles: [String]参数并调用src/lib/auth的requireAuthskipAuth在 skipAuth.ts 中同样是验证器指令只是验证函数为空实现即放行。createValidatorDirective本身在 makeDirectives.ts 中定义解析指令 schema 的名称并注册为VALIDATOR类型在字段值解析后执行校验函数。教程也提醒了一个进阶设计问题对posts/post直接skipAuth看似省事但若将来给 Post 增加publishStatus之类的字段懂 GraphQL 的人就可能借此读到草稿数据。更稳妥的做法是为公开展示单独建端点如publicPosts/publicPost内置逻辑只返回最小化数据默认的posts/post保留全量数据仅供后台使用。这样安全边界才足够清晰。记住 Redwood 的设计哲学默认安全secure by default——宁可让你意外暴露得太少也不要暴露得太多。引入认证后你会经常遇到这种来回调整某些页面或查询被默认锁住需要重新评估并放行。生成登录、注册与忘记密码页面继续用生成器补齐前端页面yarn rw g dbAuth该命令会创建 login、signup、forgot-password 三组页面并在终端输出后续定制说明。访问http://localhost:8910/login即可看到登录页。首次使用没有用户可点击登录按钮下方的链接或直接访问http://localhost:8910/signup进入注册页。dbAuth 默认把第一个输入框标注为 Username但我们的用户名实际是邮箱地址稍后可自行修改文案。用邮箱和密码注册一个用户点击 Signup 后会自动跳回首页——注册成功即自动登录此行为可通过 signupHandler 配置 更改。在SignupPage源码中可以看到跳转首页的代码教程提示留意第 21 行附近。此时再访问http://localhost:8910/admin/posts后台文章列表就能正常加载了。从 web 端实现 dbAuth.ts 可以看到signup会向后端发送{ ...attributes, method: signup }的请求forgotPassword则发送{ username, method: forgotPassword }——dbAuth 通过method字段区分不同的认证动作。添加退出登录链接登录后如何退出方案是在BlogLayout中加一个全站可见的退出入口并顺带显示当前登录用户是谁。Redwood 提供useAuthHook完整 API 见 认证文档可在任意组件中获取登录状态、用户信息并执行登出import { useAuth } from redwoodjs/auth import { Link, routes } from redwoodjs/router type BlogLayoutProps { children?: React.ReactNode } const BlogLayout ({ children }: BlogLayoutProps) { const { isAuthenticated, currentUser, logOut } useAuth() return ( header div classNameflex-between h1 Link to{routes.home()}Redwood Blog/Link /h1 {isAuthenticated ? ( div spanLogged in as {currentUser.email}/span{ } button typebutton onClick{logOut} Logout /button /div ) : ( Link to{routes.login()}Login/Link )} /div nav ul li Link to{routes.home()}Home/Link /li li Link to{routes.about()}About/Link /li li Link to{routes.contact()}Contact/Link /li /ul /nav /header main{children}/main / ) } export default BlogLayout三个解构出的成员各自含义isAuthenticated布尔值表示当前是否已登录。currentUser应用持有的当前用户信息内容由getCurrentUser决定下文详述。logOut销毁用户会话并登出的函数。理解 getCurrentUsercurrentUser 的内容从哪来页面右上角显示 Logged in as ... 时可能发现邮箱是空的——这是因为getCurrentUser默认实现出于安全考虑只返回用户的id还是那句暴露得越少越好。它位于api/src/lib/auth.{js,ts}import { AuthenticationError, ForbiddenError } from redwoodjs/graphql-server import { db } from ./db export const getCurrentUser async (session) { return await db.user.findUnique({ where: { id: session.id }, select: { id: true }, }) } export const isAuthenticated () { return !!context.currentUser } export const hasRole (roles) { if (!isAuthenticated()) { return false } // ... 支持 string / string[] 两种角色形式与 currentUser.roles 的交叉匹配 } export const requireAuth ({ roles } {}) { if (!isAuthenticated()) { throw new AuthenticationError(You dont have permission to do that.) } if (roles !hasRole(roles)) { throw new ForbiddenError(You dont have access to do that.) } }getCurrentUser是整个认证链的魔法所在它的返回值就是 web 端currentUser与 api 端context.currentUser的内容。对 dbAuth 而言传入的唯一参数session里带着已登录用户的id函数据此用 Prisma 查库。要在页面显示邮箱只需把email加入selectexport const getCurrentUser async (session) { return await db.user.findUnique({ where: { id: session.id }, select: { id: true, email: true }, }) }仓库中的安装模板 auth.ts.template 给出了完整实现并带有一整段警示注释凡是getCurrentUser返回的字段都会暴露给客户端成为浏览器 Web Inspector 里可见的currentUser新增字段前务必确认其安全性。模板还提示如果用户模型或唯一字段不同例如用db.profile.findUnique({ where: { email: session.id } })需要同步调整模型访问器与唯一字段名。另外教程中最初看到的 You dont have permission to do that. 报错正是requireAuth()在未认证时抛出的AuthenticationError——requireAuth指令的验证函数最终调用的就是它。会话密钥 SESSION_SECRETyarn rw setup auth dbAuth执行时还会顺手修改项目根目录的.env文件追加一个名为SESSION_SECRET的环境变量值为一长串随机字符。这是用户浏览器中会话 Cookie 的加密密钥永远不要共享它永远不要把它提交进仓库.env应在.gitignore中每个部署环境都应重新生成独立的值。需要新密钥时运行yarn rw g secret该命令只在终端输出新值需手动复制粘贴进.env。特别提醒如果生产环境更换了这个密钥所有用户会在下一次请求时被强制登出——因为他们浏览器里现有 Cookie 无法用新密钥解密必须重新登录以换取新密钥加密的 Cookie。总结Redwood 认证工具箱至此一篇博客的完整认证闭环已搭建完毕。整套机制可归纳为三个层次的组合拳数据层GraphQL用requireAuth锁住查询/变更用skipAuth放行公共数据。指令通过createValidatorDirective注册为验证器在字段解析后执行校验见 makeDirectives.ts。页面层路由用Private unauthenticated...包裹整组后台路由未登录自动重定向实现见 router.tsx。组件层UI在任意组件中通过useAuth()取isAuthenticated、currentUser、logOut按需渲染不同内容或执行登出。如果你只想限制某些组件、或组件中的某一段内容而不想整页受控直接从useAuth()取isAuthenticated做条件渲染即可。关于 dbAuth 的更完整能力自托管认证的安装与配置以及第三方身份提供商Auth0、Supabase、Clerk 等的接入方式可继续阅读 Redwood 官方认证文档 与 第三方提供商安装章节。附加练习用 GraphQL Playground 验证权限还记得第 3 章 Creating a Contact 末尾的 GraphQL Playground 练习吗现在认证已就位再运行一次之前对受保护字段的查询就能看到requireAuth抛出的权限错误。不过createContact这条变更因为用了skipAuth依旧可以正常创建——这正是公共能力放行、私有能力锁死的直观验证。需要说明的是目前通过 GraphQL Playground 模拟登录态体验并不友好Redwood 团队仍在持续改进这一体验。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐使用 RedwoodJS dbAuth 实现博客后台身份认证从路由私有化到会话安全的完整实战使用 RedwoodJS dbAuth 实现博客后台身份认证从路由私有化到会话安全的完整实战 在 RedwoodJS 的博客教程中构建管理后台Admin后端前端Web框架开发工具Redwood 认证实战用 dbAuth 为博客搭建完整的登录、注册与私有路由体系Redwood 认证实战用 dbAuth 为博客搭建完整的登录、注册与私有路由体系 本篇教程将带领你在 Redwood 应用中从零搭建一套自托管认证Self后端前端Web框架开发工具Redwood 教程用 dbAuth 与 PrivateSet 为博客应用搭建完整认证体系Redwood 教程用 dbAuth 与 PrivateSet 为博客应用搭建完整认证体系 本篇技术指南以 Redwood 官方教程第四章为核心完整演示如何后端前端Web框架开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考