ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RedwoodJS 安全实践指南:从认证、GraphQL 到 Serverless 函数与 Webhook 的端到端防护

RedwoodJS 安全实践指南:从认证、GraphQL 到 Serverless 函数与 Webhook 的端到端防护 后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载RedwoodJS 把安全当作框架的一等公民从 Web 端路由守卫、GraphQL 指令鉴权到 Serverless 函数的访问控制与 Webhook 签名校验框架默认提供开箱即安全的配置同时把最终责任交还给开发者。读完本文你将掌握 RedwoodJS 六大安全支柱——认证集成、GraphQL 防滥用、生产环境 Introspection 禁用、函数级鉴权、Webhook 校验与密钥环境管理——并能直接在自己的应用中落地可运行的配置与代码。本文以 RedwoodJS v6 官方安全文档security.md为主体骨架并结合当前仓库中的框架源码如 packages/api/src/webhooks/index.ts与关联文档graphql.md、serverless-functions.md、webhooks.md进行纵深展开。安全是框架的责任更是你的责任RedwoodJS 官方文档开篇即强调框架希望你能构建并部署安全的应用程序并且严肃对待安全问题。RedwoodJS 在 GitHub 上提供安全公告渠道与 CodeQL 代码扫描官方安全策略与漏洞上报联系方式也一并在文档中给出。但请务必记住文档中的警示⚠️安全是开发者自己的责任虽然 Redwood 提供了工具、实践和信息来保证应用程序安全但落实这些措施仍然是你自己的责任。官方强烈建议使用规范的密码、令牌与密钥保护手段包括受约束的沟通流程、密码管理工具以及 Doppler 之类的环境管理服务。这句话划定了 RedwoodJS 安全模型的边界框架提供机制mechanism策略policy由你制定。接下来的每一节我们都将围绕框架提供了什么机制 开发者需要落实什么策略展开。RedwoodJS 的端到端安全防线覆盖四个主要攻击面后续各节逐一展开认证AuthenticationWeb 端useAuth钩子 API 端getCurrentUser/requireAuthGraphQL指令鉴权、恶意文档拦截、Introspection 与 Playground 默认禁用Serverless 函数Functions开放端点默认裸奔需用useRequireAuth等方案收口Webhooks入站校验签名、出站签名载荷防篡改、防重放。认证redwoodjs/auth与多提供商支持认证提供商生态redwoodjs/auth是对主流 SPA 认证库的轻量封装支持以下认证提供商详见 authentication.mdNetlify Identity WidgetAuth0Azure Active DirectoryNetlify GoTrue-JSMagic Links – Magic.jsFirebase 的 GoogleAuthProviderEthereumSupabaseNhost此外Redwood 还提供了自托管方案dbAuth详见 auth/dbauth.md让你完全掌控用户数据与认证流程。官方集成以redwoodjs作用域区分例如 Auth0 集成由redwoodjs/auth-auth0-web与redwoodjs/auth-auth0-api两个 npm 包构成。通过认证设置命令即可完成接入yarn rw setup auth auth0端到端的认证链路Redwood 将认证从 Web 端打通到 API 端。Web 侧通过web/src/auth.ts中的AuthProvider与useAuth钩子接入认证上下文RedwoodApolloProvider会在每个 GraphQL 请求中携带 JWTRouter则用useAuth判断用户是否有权访问私有或角色受限路由。API 侧的处理分两步解码令牌与映射为用户对象分别由createGraphQLHandler的authDecoder与getCurrentUser两个属性完成见 authentication.mdimport { authDecoder } from redwoodjs/auth-auth0-api import { createGraphQLHandler } from redwoodjs/graphql-server import directives from src/directives/**/*.{js,ts} import sdls from src/graphql/**/*.sdl.{js,ts} import services from src/services/**/*.{js,ts} import { getCurrentUser } from src/lib/auth import { db } from src/lib/db import { logger } from src/lib/logger export const handler createGraphQLHandler({ authDecoder, getCurrentUser, loggerConfig: { logger, options: {} }, directives, sdls, services, onException: () { // Disconnect from your database with an unhandled exception. db.$disconnect() }, })useAuth钩子常用 APIuseAuth为认证提供商客户端 SDK 提供了统一接口常用成员如下完整表格见 authentication.md名称说明client创建认证提供商时使用的客户端实例多数函数在底层使用它currentUserAPI 端设置好的当前用户信息未认证时为nullgetToken返回一个 JWThasRole判断当前用户是否拥有某角色如admin或角色数组中的任一角色isAuthenticated布尔值表示用户是否已认证loading认证上下文是否正在加载logIn/logOut登录 / 登出signUp注册userMetadata直接取自认证提供商客户端的用户元数据未认证时为null在路由层可以用PrivateSet组件包裹受保护路由实现未登录不可见并支持角色级访问控制在组件内则可以组合useAuth暴露的原语自由构建登录体验。API 侧则默认锁定所有生成的 SDL 都带requireAuth指令公开访问是显式选择加入而非默认行为。GraphQL 安全默认拒绝恶意操作文档GraphQL 是 Redwood 的核心。解析 GraphQL 操作文档是非常昂贵且计算密集的操作会阻塞 JavaScript 事件循环——攻击者反复发送略作变化的复杂操作文档即可轻易拖垮 GraphQL 服务器。因此 graphql.md 的 Security 章节第 1268 行起系统阐述了 Redwood 的 GraphQL 安全默认值基于 Schema 指令的认证包含 RBAC 校验生产部署自动禁用 Introspection 与 GraphQL Playground拒绝恶意操作文档Max Aliases、Max Cost、Max Depth、Max Directives、Max Tokens防止信息泄露Block Field Suggestions、Mask Errors这些能力通过GraphQL ArmorEscape Technologies 与 The Guild 合作开发的 JS 服务器中间件以合理默认值内建开发者无需任何配置即可获得防护。默认锁定requireAuth/skipAuth/ 自定义指令默认情况下GraphQL 端点对全世界开放——SDL 中定义的任何类型与字段任何人都可以查询。Redwood 鼓励默认安全生成 SDL 或 Service 时所有查询与 Mutation 都默认为requireAuth。应用构建或服务器启动时Redwood 会检查所有查询和 Mutation 是否都应用了requireAuth、skipAuth或自定义指令否则构建失败✖ Verifying graphql schema... Building API... Cleaning Web... Building Web... Prerendering Web... You must specify one of requireAuth, skipAuth or a custom directive for - contacts Query - posts Query - post Query - updatePost Mutation - deletePost Mutation或在开发服务器启动时报 Schema validation failedgen | Generating TypeScript definitions and GraphQL schemas... gen | 47 files generated api | Building... Took 593 ms api | [GQL Server Error] - Schema validation failed api | ---------------------------------------- api | You must specify one of requireAuth, skipAuth or a custom directive for api | - posts Query api | - createPost Mutation api | - updatePost Mutation api | - deletePost Mutation修正方式为相应查询与 Mutation 补上恰当的指令。requireAuth用于强制认证——在 SDL 中给任何查询或字段加上它即可type Mutation { createPost(input: CreatePostInput!): Post! requireAuth updatePost(id: Int!, input: UpdatePostInput!): Post! requireAuth deletePost(id: Int!): Post! requireAuth }该指令会调用你应用api/src/lib/auth.{js|ts}中实现的requireAuth()函数判断用户是否已认证 / 是否具备期望角色。新应用中的auth.ts是桩实现接入认证提供商后才会执行真正的认证检查// ... export const isAuthenticated (): boolean { return true // replace with the appropriate check } // ... export const requireAuth ({ roles }: { roles: AllowedRoles }) { if (isAuthenticated()) { throw new AuthenticationError(You dont have permission to do that.) } if (!hasRole({ roles })) { throw new ForbiddenError(You dont have access to do that.) } }字段级认证requireAuth不仅能加在查询 / Mutation 上也能加在任意字段上type Post { id: Int! title: String! body: String! requireAuth authorId: Int! author: User! createdAt: DateTime! }基于角色的访问控制RBACrequireAuth支持通过roles参数限定允许执行操作的角色type Mutation { createPost(input: CreatePostInput!): Post! requireAuth(roles: [AUTHOR, EDITOR]) updatePost(id: Int!, input: UpdatePostInput!): Post! requireAuth(roles: [EDITOR]) deletePost(id: Int!): Post! requireAuth(roles: [ADMIN]) }skipAuth用于显式放行公开操作type Query { posts: [Post!]! skipAuth post(id: Int!): Post skipAuth }生产环境默认禁用 Introspection 与 PlaygroundIntrospection 允许客户端查询 schema 支持哪些查询GraphQL Playground 则提供交互式探索 schema 与执行查询的界面。两者都可能泄露关于数据模型、数据、查询与 Mutation 的敏感信息因此生产部署的最佳实践是禁用它们——Redwood 默认仅在开发环境process.env.NODE_ENV development启用 Introspection 与 Playground即 http://localhost:8911/graphql。确有需要时可通过createGraphQLHandler的allowIntrospection与allowGraphiQL选项显式开启见 graphql.md 第 1447 行起export const handler createGraphQLHandler({ authDecoder, getCurrentUser, loggerConfig: { logger, options: {} }, directives, sdls, services, allowIntrospection: true, // enable introspection in all environments allowGraphiQL: true, // enable GraphiQL Playground in all environments onException: () { // Disconnect from your database with an unhandled exception. db.$disconnect() }, })⚠️警告在生产环境开启 Introspection 存在安全风险它允许用户访问 schema、查询与 Mutation 的信息。请谨慎使用并妥善保护你的 GraphQL API。你可能只想开 Introspection 而不开 GraphiQL或反之——例如只想测试已知查询而不共享全部可能的操作与类型。GraphQL Armor恶意文档的五道闸门GraphQL Armor 以逐插件方式完全可配置。只需在createGraphQLHandler中提供自定义armorConfigexport const handler createGraphQLHandler({ authDecoder, getCurrentUser, loggerConfig: { logger, options: {} }, directives, sdls, services, armorConfig, // custom GraphQL Security configuration onException: () { // Disconnect from your database with an unhandled exception. db.$disconnect() }, })例如默认最大查询深度为 6改为 2 层armorConfig: { maxDepth: { n: 2 } },五道默认开启的防护闸门如下参数与示例均来自 graphql.md 第 1530–1897 行1. Max Aliases默认启用默认 15限制单个文档中的别名数量。别名可重命名查询结果字段攻击者可用大量别名构造结构被操纵的昂贵查询。配置方式{ maxAliases: { enabled: true, n: 15, } }2. Cost Limit默认启用默认 maxCost 5000对入站查询做成本分析阻止过于昂贵的请求DoS 攻击尝试。成本由字段种类与深度决定标量字段值 1对象字段值 2深度是乘数因子COST FIELD_KIND_COST * (DEPTH * DEPTH_COST_FACTOR) TOTAL_COST SUM(COST)默认参数objectCost: 2、scalarCost: 1、depthCostFactor: 1.5。若TOTAL_COST超过maxCostGraphQL 执行被终止并拒绝请求。配置方式{ costLimit: { enabled: true, maxCost: 5000, // maximum cost of a request before it is rejected objectCost: 2, // cost of retrieving an object scalarCost: 1, // cost of retrieving a scalar depthCostFactor: 1.5, // multiplicative cost of depth } }成本计算示例查询{ profile { me { id user } } }中两个标量id、user各值 1处于深度 1因子 1.5即 2 × (1 × 1.5) 3父对象me值 2总成本 2 3 5。注意操作定义如query与profile命名不计入成本。3. Max Depth默认启用默认 6 层限制文档深度防范利用 schema 关系构造的循环查询cyclical query——这类查询层层嵌套author → posts → author → posts …会耗尽数据库与计算资源。示例中的循环查询深度已达 8query cyclical { author(id: jules-verne) { posts { author { posts { author { posts { author { ... # more deep nesting! } } } } } } } }配置方式{ maxDepth: { enabled: true, n: 6, } }4. Max Directives默认启用默认 50限制文档中指令数量。攻击者可组合include/skip构造需要大量计算却只返回少量数据的请求。配置方式{ maxDirectives: { enabled: true, n: 50, } }5. Max Tokens默认启用默认 1000限制文档中的 GraphQL 词法单元token数量。例如{ me { id user } }的 token 为query、{、me、{、id、user、}、}共 8 个。若配置maxTokens: { n: 2 }将抛出Syntax Error: Token limit of 2 exceeded, found 3.。注意报错中的 found 值并非 token 总数而是超限那一刻的值即 n 1。配置方式{ maxTokens: { enabled: true, n: 1000, } }防信息泄露字段建议屏蔽与错误掩码Block Field Suggestions默认启用错误请求时 GraphQL 会建议相似字段如Cannot query field sta on type Media. Did you mean stats, staff, or status?这会泄露 schema 结构——即使已禁用 Introspection。默认启用屏蔽也可自定义掩码{ blockFieldSuggestion: { enabled: true, } } // 或自定义掩码 { blockFieldSuggestion: { mask: REDACTED }, }Error Masking错误掩码许多 GraphQL 服务器会把错误细节泄露给外部——数据库连接失败、特定字段的存在性等都可能被客户端渲染或记录。Redwood 对意外错误开箱即用地屏蔽敏感堆栈信息原始错误与消息会写入 GraphQL logger 供你排查响应中则替换为默认消息Something went wrong。自定义默认错误消息可通过createGraphQLHandler的defaultError设置export const handler createGraphQLHandler({ loggerConfig: { logger, options: {} }, directives, sdls, services, defaultError: Sorry about that, // Customize the error message onException: () { db.$disconnect() }, })若想与客户端共享特定错误消息则使用 Redwood 内置错误从redwoodjs/graphql-server导入灵感源自 Apollo Server Error codesSyntaxError—— 发生了未指定的错误ValidationError—— 输入到 Service 的数据无效AuthenticationError—— 认证失败ForbiddenError—— 无权访问UserInputError—— 缺少输入到 Service 的数据使用这些错误时所提供的消息不会被掩码而是直接出现在 GraphQL 响应中import { UserInputError } from redwoodjs/graphql-server // ... throw new UserInputError(An email is required.)需要自定义错误例如集成第三方 API 时控制错误如何呈现给客户端可继承RedwoodErrorexport class MyCustomError extends RedwoodError { constructor(message: string, extensions?: Recordstring, any) { super(message, extensions) } }可选的纵深防御在 GraphQL Armor 之外借助 Yoga Envelop Plugin 生态Redwood 的 GraphQL 端点还可以扩展CSRF 防护、速率限制Rate Limiting等能力进一步收口攻击面。Serverless 函数开放端点默认裸奔需要主动收口认清威胁模型部署后自定义 Serverless 函数就是一个开放的 API 端点——任何人都能访问并执行它要求的一切任务。这在很多场景下是合理且期望的行为但当函数与第三方交互如发送邮件或从数据库检索敏感信息时你需要确保只有来自可信来源的已验证请求才能调用它。某些场景甚至需要限制单位时间内的调用次数以抵御拒绝服务类攻击。方案一用 Redwood 用户认证保护函数Serverless 函数可以复用 GraphQL 指令保护 Service 的同一套用户认证策略——通过useRequireAuth包装器详见 serverless-functions.md 第 718 行起。useRequireAuth配置 handler 的context使你在函数中可以使用任何requireAuth相关的认证辅助函数。实施步骤从redwoodjs/graphql-server导入useRequireAuth从src/lib/auth导入自定义getCurrentUser与isAuthenticated检查导入你的认证提供商的authDecoder照常实现函数体但不要导出如下例的myHandler将实现、getCurrentUser、authDecoder传给useRequireAuth包装器并导出其返回值检查isAuthenticated()未认证时返回401状态码import type { APIGatewayEvent, Context } from aws-lambda import { authDecoder } from redwoodjs/auth-dbauth-api import { useRequireAuth } from redwoodjs/graphql-server import { getCurrentUser, isAuthenticated } from src/lib/auth import { logger } from src/lib/logger const myHandler async (event: APIGatewayEvent, context: Context) { logger.info(Invoked myHandler) if (isAuthenticated()) { logger.info(Access myHandler as authenticated user) return { statusCode: 200, headers: { Content-Type: application/json, }, body: JSON.stringify({ data: myHandler function, }), } } else { logger.error(Access to myHandler was denied) return { statusCode: 401, } } } export const handler useRequireAuth({ handlerFn: myHandler, getCurrentUser, authDecoder, })此后任何使用context的地方——如 Service 中使用hasRole()或isAuthenticated()——currentUser都会被正确设置requireAuth相关函数能验证认证状态与角色。简言之isAuthenticated()、hasRole()、requireAuth()都可以在 Serverless 函数中直接使用。调用受保护函数时请求需携带如下请求头函数没有登录流程useRequireAuth假定用户已认证并持有 JWT 访问令牌Authorization: Bearer myJWT.accesstoken.signature auth-provider: supabase Content-Type: application/jsonauth-provider应用使用的认证提供商类型如dbAuthAuthorizationBearer 令牌JWT 访问令牌若使用 dbAuth还需携带 dbAuth Cookie。官方提示如果打算实现需要用户认证的功能优先选择 GraphQL、认证指令与 Service 的组合方案。方案二非用户认证场景考虑 Webhooks如果你需要保护的端点并非基于用户认证官方建议考虑使用 webhooks.md 中的签名载荷 验证器方案详见下一节。其他防护手段除认证外日志Visibility via Logging、速率限制Rate Limiting与白名单Whitelisting也是保护函数免遭滥用或误用的常用手段。完整的 Other security considerations 章节位于 serverless-functions.md 第 828 行起。Webhooks入站要验签出站要签名Webhook 是第三方服务在事件发生时通知 RedwoodJS 应用的常见方式——一种消息 / 自动化形式允许 Web 应用相互通信并在事件发生时实时推送数据。由于每个 Webhook 都会调用你 API 中的一个函数端点你必须确保它只在应该运行时运行。这意味着你需要验证它来自你预期的地方Verify it comes from the place you expect信任该方Trust the party确认载荷未被篡改Know the payload sent in the hook hasnt been tampered with确保 Webhook 不会被重复处理或重放Ensure that the hook isnt reprocessed or replayed底层实现redwoodjs/api/webhooks的验签机制入站 Webhook 的验签能力由redwoodjs/api包中的webhooks模块提供源码位于 packages/api/src/webhooks/index.tsDEFAULT_WEBHOOK_SIGNATURE_HEADER RW-WEBHOOK-SIGNATURE默认签名请求头signatureFromEvent从 Lambda 事件的指定请求头默认RW-WEBHOOK-SIGNATURE大小写不敏感提取签名verifyEvent(type, { event, payload, secret, options })按验证器类型验证事件载荷签名验证失败时抛出WebhookVerificationErrorsecret默认取DEFAULT_WEBHOOK_SECRETverifySignature底层签名比对函数signPayload出站 Webhook 签名函数。eventBody内部处理还会检查event.isBase64Encoded对 base64 编码的载荷先解码再验证——这是兼容网关编码行为的重要细节。开发 / 测试环境可跳过验证在测试或开发环境可以使用skipVerifier这样不必与团队其他开发者共享密钥。典型做法是设置环境变量WEBHOOK_VERIFICATIONskipVerifier并在verifyEvent(process.env.WEBHOOK_VERIFICATION, { event })中使用import type { APIGatewayEvent } from aws-lambda import { verifyEvent, WebhookVerificationError } from redwoodjs/api/webhooks import { logger } from src/lib/logger export const handler async (event: APIGatewayEvent) { const livestormInfo { webhook: livestorm } const webhookLogger logger.child({ livestormInfo }) try { verifyEvent(skipVerifier, { event }) const data JSON.parse(event.body) webhookLogger.debug({ payload: data }, Data from Livestorm) return { headers: { Content-Type: application/json, }, statusCode: 200, body: JSON.stringify({ data }), } } catch (error) { if (error instanceof WebhookVerificationError) { webhookLogger.warn(Unauthorized) return { statusCode: 401, } } else { webhookLogger.error({ error }, error.message) return { headers: { Content-Type: application/json, }, statusCode: 500, body: JSON.stringify({ error: error.message }), } } } }出站 Webhook签名你的载荷对于出站 Webhookredwoodjs/api/webhooks导出的signPayload会使用某种验证方法为载荷签名生成Webhook 签名。拿到签名后可以按自定义名称将其加入请求的 HTTP 请求头再发送请求完整示例见 webhooks.md 第 771 行起。密钥与令牌环境变量的安全管理认证提供商密钥、数据库连接串、Webhook 签名密钥等敏感信息应通过环境变量管理。RedwoodJS 对 web 与 api 两侧的环境变量加载有明确约定详见 environment-variables.md以REDWOOD_ENV_前缀声明的变量会被注入 Web 端代码非前缀变量仅在 API 端Serverless 函数可用不会暴露给浏览器.env文件中的变量由 Redwood 在开发与构建时读取但生产环境的敏感值应由部署平台的密钥管理能力注入。原则密钥永不落入客户端代码API 端密钥通过部署平台的安全机制注入Web 端只保留经过REDWOOD_ENV_显式标记的非敏感配置。安全基线小结结合 security.md 与仓库源码RedwoodJS 应用的安全基线可归纳为一张自检清单攻击面框架默认机制开发者落实事项认证redwoodjs/auth多提供商封装、useAuth、PrivateSet路由守卫实现getCurrentUser/requireAuth选择并配置认证提供商GraphQL指令鉴权requireAuth默认、GraphQL Armor 五道闸门、Introspection/Playground 生产禁用、错误掩码为所有操作声明恰当的指令按需调整armorConfig谨慎开启生产 IntrospectionServerless 函数useRequireAuth包装器、Webhook 验签、日志 / 限流 / 白名单建议按威胁模型为每个开放端点选择防护方案WebhooksverifyEvent/signPayload/skipVerifier默认签名头RW-WEBHOOK-SIGNATURE管理好签名密钥确认来源、信任方、载荷完整性与防重放密钥管理.envREDWOOD_ENV_前缀约定使用密码管理器与 Doppler 等环境管理服务杜绝密钥进代码库RedwoodJS 的哲学是默认安全、显式放开生成即带requireAuth、生产即禁 Introspection、GraphQL 端点即配 Armor。剩下的工作——选择认证策略、维护密钥、为每个端点评估威胁模型——则是每位 RedwoodJS 开发者必须亲自完成的安全功课。沿着本文引用的 graphql.md、serverless-functions.md、webhooks.md 与 authentication.md 继续深入即可把每道防线打磨到生产级。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐RedwoodJS 安全实战指南认证、GraphQL 防护、函数与 Webhook 的端到端加固RedwoodJS 安全实战指南认证、GraphQL 防护、函数与 Webhook 的端到端加固 导读 本指南围绕 RedwoodJS v4.x 官方安全文档后端前端Web框架开发工具RedwoodJS 应用安全实践认证、GraphQL 防护、函数保护与 Webhook 签名验证RedwoodJS 应用安全实践认证、GraphQL 防护、函数保护与 Webhook 签名验证 RedwoodJS 将安全视为构建与部署 Web 应用的基础后端前端Web框架开发工具RedwoodJS 安全加固实战指南认证、GraphQL 防护与 Webhook 签名验证RedwoodJS 安全加固实战指南认证、GraphQL 防护与 Webhook 签名验证 RedwoodJS 将安全视为框架的默认行为而非事后补救api后端前端Web框架开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表