- 后端
【免费下载链接】sails
Realtime MVC Framework for Node.js
导读
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阶段:
- 调用
sails.modules.loadResponses(...)加载应用自定义的响应模块(即api/responses/目录下的文件,加载逻辑见 lib/hooks/moduleloader/index.js 中的loadResponses,它使用includeAll.optional按文件名生成响应键名); - 使用
_.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方法冲突(如view、send、json等,见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),保证接口行为稳健; - 不要覆盖保留方法名:响应文件名不能与
view、send、json等保留键冲突(响应名解析规则见 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
相关推荐
Dexter权限拒绝处理:永久拒绝与临时拒绝的差异指南
Dexter权限拒绝处理:永久拒绝与临时拒绝的差异指南 在Android应用开发中,权限管理是一个至关重要的环节。Dexter作为一款优秀的Android权限请
移动开发认证鉴权Noctalia Shell社区贡献指南:从代码提交到文档维护
Noctalia Shell社区贡献指南:从代码提交到文档维护 Noctalia Shell是一款为Wayland精心打造的时尚简约桌面外壳,凭借其现代化的设计
桌面应用Sails 框架 `res.serverError()` 详解:500 错误响应方法与自定义错误页
Sails 框架 res.serverError 详解:500 错误响应方法与自定义错误页 本篇指南深入讲解 Sails 实时 MVC 框架内置响应方法 res
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考