news 2026/9/21 0:15:40

Sails 中 res.forbidden() 响应方法:403 权限拒绝的标准出口与自定义实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sails 中 res.forbidden() 响应方法:403 权限拒绝的标准出口与自定义实战
  • 后端

【免费下载链接】sails

Realtime MVC Framework for Node.js

项目地址:https://gitcode.com/gh_mirrors/sa/sails
点击查看免费下载

导读

res.forbidden()是 Sails(Realtime MVC Framework for Node.js)内置的响应方法之一,用于向客户端发送 HTTP 403("Forbidden")响应,明确表示"当前请求不被允许"。它适用于登录态校验失败、越权访问、CSRF 校验不通过、策略(policy)拒绝等所有"请求本身合法但权限不足"的场景。读完本文,你将掌握res.forbidden()的调用方式、默认行为、底层实现原理,以及通过api/responses/forbidden.js覆盖默认行为、通过路由目标语法直接绑定响应模块的完整实战方案。

方法语义:什么时候该返回 403

HTTP 403 状态码属于 4xx 客户端错误 系列。根据 官方参考文档 的定义,res.forbidden()用来向客户端发送 403 响应,表示"该请求不被允许"(a request is not allowed)。这通常意味着用户代理(浏览器、App、脚本)尝试执行了它无权执行的操作——例如试图修改另一个用户的密码。

在 Sails 应用中,最常见的触发场景包括:

  • 登录态校验失败:访问需要登录的接口时,req.session.userId不存在;
  • 越权访问:用户试图操作不属于自己的资源(如修改他人资料、删除他人订单);
  • 策略(Policy)拒绝:某个路由被false策略或自定义策略拦截;
  • CSRF 校验失败:请求缺少或携带了错误的 CSRF 令牌。

需要与 401 区分:401 表示"未认证"(不知道你是谁),403 表示"已认证但无权限"(知道你是谁,但不允许你做这件事)。res.forbidden()专门负责后者。

基本用法

在 action、helper 或 policy 中,直接调用即可:

return res.forbidden();

按照惯例,调用时应使用return语句提前返回(原因见下文"终止性"说明)。文档给出的典型示例是登录守卫:

if ( !req.session.userId ) { return res.forbidden(); }

req.session.userId为空(用户未登录)时,请求立即以 403 终止,后续的业务逻辑不会继续执行。

默认行为细节

与其他内置响应方法一样,res.forbidden()的行为是可定制的(详情见 Custom Responses)。在未做任何定制时,它默认完成两件事:

行为说明
状态码响应状态码设置为403
响应体发送字符串"Forbidden"

也就是说,客户端会收到一个状态码为 403、正文为Forbidden的响应。

底层实现:sendStatus(403)

从源码结构看,Sails 核心的默认实现位于 lib/hooks/responses/defaults/forbidden.js,其完整逻辑非常简洁:

module.exports = function forbidden () { // Get access to `res` var res = this.res; // Send status code and "Forbidden" message return res.sendStatus(403); };

关键点:

  • 函数体通过this.res拿到当前请求的响应对象(this上下文由 responses 钩子在挂载时注入,见下文);
  • 直接委托给 Express 风格的res.sendStatus(403),该方法会同时完成"设置状态码 403"与"发送默认状态消息Forbidden"两件事,与文档描述的默认行为完全一致;
  • 默认实现没有读取req.wantsJSON、不渲染视图、也不接收任何业务参数——它就是一个纯粹的"标准 403 出口"。

源码级原理:响应方法如何挂载到 res 上

res.forbidden()并不是凭空出现的,而是由 Sails 的responses 钩子在请求到达时动态挂载到每个res对象上的。理解这条链路有助于你判断"自定义后何时生效"。

1. 钩子加载与默认合并

responses 钩子的入口位于 lib/hooks/responses/index.js。在loadModules阶段:

  1. 调用sails.modules.loadResponses(...)加载应用自定义的响应模块(即api/responses/目录下的文件,加载逻辑见 lib/hooks/moduleloader/index.js 中的loadResponses,它使用includeAll.optional按文件名生成响应键名);
  2. 使用_.defaults()把内置默认响应作为兜底合并进去,其中就包括:
_.defaults(responseDefs, { ok: require('./defaults/ok'), negotiate: require('./defaults/negotiate'), notFound: require('./defaults/notFound'), serverError: require('./defaults/serverError'), forbidden: require('./defaults/forbidden'), badRequest: require('./defaults/badRequest') });

注意_.defaults的语义:只有应用没有自定义同名响应时,默认实现才会被使用。这正是"如果在你的应用里不存在forbidden.js,Sails 将使用默认行为"这一文档声明的代码依据。

此外,钩子还会检查自定义响应名是否与保留的res方法冲突(如viewsendjson等,见reservedResKeys数组),冲突时会抛出E_INVALID_CUSTOM_RESPONSE类错误,避免覆盖 Express 原生方法。

2. 请求级挂载

routes.before['all /*']中间件(同样位于 lib/hooks/responses/index.js)中,钩子遍历sails.middleware.responses,把每个响应方法绑定到当前res对象:

_.each(sails.middleware.responses, function eachMethod(responseFn, name) { res[name] = responseFn.bind({ req: req, res: res }); });

bind注入了{ req, res }作为this上下文——这就是默认实现里this.res的来源,也是自定义响应函数里this.req/this.res的来源。由于该挂载发生在每个请求的早期阶段,你在 action、policy、自定义中间件中调用res.forbidden()时,它一定已经就绪。

3. 终止性:必须搭配 return

文档特别强调:res.forbidden()是"终止性"(terminal)方法,通常应该是某个请求处理流程的最后一行代码——这正是整篇文档反复建议使用return res.forbidden()的原因。因为一旦调用,响应就已确定(状态码与正文已发出或即将发出),后续代码继续执行会导致"响应头已发送后再次写入"之类的错误,或产生不可预期的行为。

覆盖默认行为:自定义 api/responses/forbidden.js

res.forbidden()是"用户态"(userland)响应方法,可以被覆盖或修改:它运行的是api/responses/forbidden.js中定义的方法;如果应用中没有forbidden.js,则回退到默认行为。这是 Sails 所有内置响应方法(res.ok()res.serverError()res.notFound()res.badRequest()等)的通用扩展机制,参见 AddingCustomResponse。

在项目根目录创建api/responses/forbidden.js

/** * api/responses/forbidden.js * * 自定义 403 (Forbidden) 处理器。 * 覆盖 Sails 内置的 res.forbidden() 默认行为。 * * 用法: * return res.forbidden(); * return res.forbidden('无权修改他人的资料'); */ module.exports = function forbidden(message) { var req = this.req; var res = this.res; var sails = req._sails; var statusCode = 403; // 构造响应体 var result = { status: statusCode, message: message || 'Forbidden' }; // 记录日志(verbose 级别,仅调试可见) if (message) { sails.log.verbose('Sending 403 ("Forbidden") response: \n', message); } // 如果客户端期望 JSON,直接返回 JSON 结构 if (req.wantsJSON) { return res.status(statusCode).json(result); } // 否则渲染自定义错误页(可选) res.status(statusCode); return res.view('403', result, function(err) { // 视图渲染失败时回退为纯文本 if (err) { return res.sendStatus(statusCode); } }); };

要点说明:

  • this.req/this.res可用:挂载时钩子注入的上下文保证了你可以在自定义响应里访问当前请求与响应;
  • req.wantsJSON协商:Sails 请求解释器会在req上暴露wantsJSON标志,可按客户端期望返回 JSON 或 HTML,这与 Sails "transport-agnostic"(HTTP 与 WebSocket 兼容)的设计一致(参见 res 总览);
  • 视图渲染可选:如果应用存在views/403.ejs等视图模板,可渲染友好错误页;渲染失败再回退为sendStatus(403),保证接口行为稳健;
  • 不要覆盖保留方法名:响应文件名不能与viewsendjson等保留键冲突(响应名解析规则见 lib/hooks/responses/index.js 的reservedResKeys与 lib/hooks/moduleloader/index.js 的loadResponses)。

进阶:把路由直接绑定到响应模块

responses 钩子还"教"路由器理解response目标语法:你可以把某个路由地址直接绑定到一个响应模块,无需写 action。其实现位于 lib/hooks/responses/onRoute.js,当遇到route:typeUnknown事件且route.target.response存在时,会执行:

sails.router.bind(route.path, function(req, res) { res[route.target.response](); }, route.verb, route.options);

例如,在config/routes.js中声明"任何对/admin/*的 GET 请求一律返回 403":

module.exports.routes = { 'get /admin/sweet-dashboard-or-report-or-something': { response: 'forbidden' } };

该语法同样支持自定义响应名(如{ response: 'notImplemented' })。若绑定的响应名不存在,钩子会记录sails.log.error并忽略该绑定,而不会让应用崩溃——这也是"无效响应名"场景下的容错行为。

框架内部的真实调用场景

res.forbidden()不仅是给应用开发者使用的 API,Sails 核心模块也在多处直接调用它,从侧面印证了它的语义定位:

  • 策略拒绝:在 lib/hooks/policies/index.js 中,当路由的某个策略被显式设为false时,Sails 生成一个neverAllow函数并调用res.forbidden()立即拒绝请求:
var neverAllow = function neverAllow (req, res) { return res.forbidden(); }; neverAllow._middlewareType = 'POLICY: false (neverAllow)';
  • CSRF 校验失败:在 lib/hooks/security/csrf/index.js 中,当请求令牌缺失或不匹配时,调用res.forbidden('CSRF mismatch')(生产环境与 socket 请求场景均如此)。注意这里向自定义响应传入了参数'CSRF mismatch',如果你自定义了forbidden.js,需要像上文示例一样接收并处理该参数。

  • 错误协商分发:在 lib/hooks/responses/defaults/negotiate.js 中,res.negotiate(err)根据错误状态码分发到对应响应方法:

if (statusCode === 403) { return res.forbidden(body); } if (statusCode === 404) { return res.notFound(body); } if (statusCode >= 400 && statusCode < 500) { return res.badRequest(body); }

也就是说,任何携带 403 状态码的错误经过res.negotiate()时,最终都会落到res.forbidden()。因此,自定义api/responses/forbidden.js会同时影响这些内部调用点的输出形态。

与其他内置响应方法的分工

Sails 共内置六组默认响应方法(合并逻辑见 lib/hooks/responses/index.js),res.forbidden()在其中扮演"权限拒绝"的专属角色:

方法状态码语义
res.ok()200请求成功
res.badRequest()400请求本身非法(参数错误)
res.forbidden()403已识别请求方但无权限
res.notFound()404资源不存在(Sails 在未匹配任何路由时会自动调用,见 notFound 默认实现)
res.serverError()500服务器内部错误
res.negotiate()动态根据错误状态码分发到上述方法

在权限控制场景中的最佳实践是:登录状态校验用res.forbidden()(或交给 policy),参数校验用res.badRequest(),资源缺失用res.notFound()。结合 Policies 与 Security/CSRF 文档,你可以在 Sails 中构建一套语义清晰的 4xx 错误出口体系。

小结

  • res.forbidden()发送403 + "Forbidden"响应,用于表达"请求不被允许";
  • 默认实现即 lib/hooks/responses/defaults/forbidden.js 中的res.sendStatus(403)
  • 它由 responses 钩子在每个请求上通过bind({req, res})挂载,可被api/responses/forbidden.js完全覆盖,也可通过{ response: 'forbidden' }路由语法直接绑定;
  • Sails 的策略(false策略的neverAllow)、CSRF 校验、res.negotiate()等内部机制都在使用它,自定义时记得兼容"可能携带参数"(如'CSRF mismatch')的调用方式;
  • 调用时务必使用return res.forbidden()以利用其终止性,避免后续代码继续执行。
  • 后端

【免费下载链接】sails

Realtime MVC Framework for Node.js

项目地址:https://gitcode.com/gh_mirrors/sa/sails
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 0:15:11

天勤量化开发包实战:从行情订阅到实盘交易

简介&#xff1a;天勤量化开发包&#xff08;TqSdk&#xff09;是一套面向期货、期权、股票量化交易的Python开发工具包&#xff0c;服务于交易策略研究员、程序化交易开发者及金融科技学习者。它将历史数据、实时行情、策略回测、模拟交易、实盘交易、运行监控与风险管理整合为…

作者头像 李华
网站建设 2026/9/21 0:12:06

V-M不可逆双闭环直流调速系统课程设计全解析

简介&#xff1a;一套面向自动化、电气工程及其自动化专业学生的V-M不可逆双闭环直流调速系统课程设计资料&#xff0c;围绕完整设计流程展开。内容涵盖设计任务书解读、主电路选型与参数计算、晶闸管整流装置及保护电路设计、转速电流双闭环调节器的动态整定&#xff0c;并给出…

作者头像 李华
网站建设 2026/9/21 0:11:32

Worktrunk:用Git Worktree管理并行AI Agent工作区的实战复盘

过去大半年我一直在折腾一件事&#xff1a;同时让三四个 AI 编程 Agent 在同一个项目上并行干活。Codex CLI、Claude Code 这类工具确实能大幅提速&#xff0c;但真正卡住我的不是模型能力&#xff0c;而是 Git 仓库怎么扛住多路并发的写入。分支不够用、工作区互相污染、提交历…

作者头像 李华
网站建设 2026/9/21 0:08:01

短视频平台流量数据采集与分析闭环实践

简介&#xff1a;本资源是一套面向计算机专业本科生的高分毕业设计实战项目&#xff0c;聚焦短视频平台流量数据的自动化爬取与多维分析&#xff0c;适用于正在开展毕设、课程设计或期末大作业的学生&#xff0c;以及希望提升Python工程实践能力的学习者。压缩包共532个文件&am…

作者头像 李华
网站建设 2026/9/21 0:02:06

基于Java校园二手交易系统实战:数据库设计、状态机与并发控制

简介&#xff1a;一份基于Java的校园二手交易系统毕业设计文档&#xff0c;面向计算机相关专业学生与SSM框架实践入门者。资源围绕商品类别管理、商品信息管理、订单管理、用户管理四大核心模块&#xff0c;完整呈现从需求分析、数据库设计到SSM框架整合开发的全过程&#xff0…

作者头像 李华