十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Sails Policies 权威指南:从 ACL 配置到授权中间件源码解析

Sails Policies 权威指南:从 ACL 配置到授权中间件源码解析 后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载Policies策略是 Sails 实时 MVC 框架内置的授权与访问控制机制它允许你在 action 执行之前运行一段逻辑从而决定是否继续处理该请求最常见的用途是把某些 action 限制为仅登录用户可访问。本文将基于 Sails 官方文档与当前仓库源码lib/hooks/policies/index.js、lib/router/bind.js等系统讲解config/policies.js声明式 ACL 的完整语法控制器、独立 action、全局通配符、优先级并深入剖析 policy 的加载、绑定与执行链路最后给出从简单登录校验到动态权限Helper 方案的完整实战示例。OverviewPolicies 到底是什么在 Sails 中Policies 是授权与访问控制的通用工具它们在 action 运行之前执行某些逻辑以此判断是否继续处理该请求。Policies 最常见的应用场景是将某些 action 限制为仅登录用户logged-in users可访问。注意Policies只作用于控制器controllers和 action不作用于视图views。如果你在 routes.js 配置文件 中定义了一条直接指向某个视图的路由那么不会有任何 policy 应用到它。为确保 policy 生效你应该改为定义一个渲染该视图的 action然后把路由指向这个 action。从实现上看每个 policy 本质上是 Express 风格的中间件函数(req, res, next)在目标 action 之前按顺序执行。这一点在 config/policies.js 解剖文档 中有明确说明policy 文件如api/policies/isLoggedIn.js可以放入api/policies/目录之后即可通过其文件名去掉扩展名如isLoggedIn来引用。什么时候该用 PoliciesSails 官方建议避免在应用里实现过多或过于复杂的 policy。当实现细粒度、基于角色的权限时应依靠你的 actions 来拒绝未经授权的访问同时action 也应当负责对响应中发送的视图局部变量view locals和 JSON 响应数据做必要的个性化处理。例如当你需要实现用户级或角色级权限时最直接的做法是在控制器 action 的顶部处理相关校验——无论是内联编写还是调用一个 helper。遵循这一最佳实践将显著提升代码的可维护性。简单地说Policies 擅长的是二元的yes/no访问控制它非常适合检查用户是否已登录当前登录用户是否为超级管理员这类简单判断。而复杂权限取决于谁和想做什么更适合放到 action 或 helper 中处理详见下文动态权限一节。用 Policies 保护 action 与控制器Sails 内置了一个位于config/policies.js的ACL访问控制列表。该文件用于把 policy 映射到 action 和控制器上。这个文件是声明式declarative的它描述的是应用权限应该是什么样what而不是如何实现how。这让新开发者更容易理解当前配置也让应用在需求随时间变化时更加灵活。config/policies.js是一个字典dictionary其属性和值的形态取决于你是在为控制器还是为独立 action 配置 policy。将 Policies 应用于控制器要将 policy 应用于控制器请在config/policies.js字典中使用控制器名称作为属性名并把它的值设为另一个字典该字典将控制器中的 action 映射到要应用的 policy 上。使用*表示所有未映射的 action。policy 的名称与其文件名相同去掉扩展名module.exports.policies { UserController: { // 默认要求请求来自已登录用户 // 运行 api/policies/isLoggedIn.js 中的 policy *: isLoggedIn, // 只允许管理员用户删除其他用户 // 运行 api/policies/isAdmin.js 中的 policy delete: isAdmin, // 允许任何人访问 login action即使未登录 login: true } };将 Policies 应用于独立 action要为某个或多个独立 action 应用 policy请使用action 路径相对于api/controllers作为config/policies.js字典中的属性名并把值设为应应用于这些 action 的 policy 或 policy 数组。通过在 action 路径末尾使用通配符*你可以把 policy 应用于所有以该路径开头的 action。下面是与上面相同的策略集改写为应用于独立 action 的写法module.exports.policies { user/*: isLoggedIn, user/delete: isAdmin, user/login: true }注意这个例子与基于控制器的 policy 写法略有不同——isLoggedInpolicy 将应用于api/controllers/user文件夹及其子文件夹中的所有 action但user/delete和user/login除外原因见下一节。Policy 的排序与优先级Policies 不会级联do not cascade这一点非常重要。在上面的例子中isLoggedInpolicy 会应用于UserController.js文件中的所有 action或位于api/controllers/user下的独立 action但delete和login除外。如果希望给一个 action 应用多个 policy请用数组列出这些 policygetEncryptedData: [isLoggedIn, isInValidRegion]从源码角度来印证这种非级联、精确覆盖的机制在 lib/hooks/policies/index.js 的buildPolicyMap()中所有 policy 键会被按字母序排序使更具体的键如user/foo排在更宽泛的键如user/*之后随后对于*全局键会为排在它后面的每个目标追加!target形式的排除项对于/*通配符键也会对后续匹配到的目标追加!前缀从而保证更精确的映射覆盖更宽泛的映射——注释里明确写着for now policies are NOT cumulative目前 policies 不会累积。最终这些target,!exclude1,!exclude2形式的逗号分隔字符串会通过sails.registerActionMiddleware(policies, targets)注册见 bindPolicies。而真正匹配时lib/router/bind.js 会把每个注册键按,拆分成目标数组并排序!开头的排前面先检查否定目标!...是否匹配 action identity再检查肯定目标*、xxx/*通配、精确匹配从而决定该 policy 是否加入待执行中间件链。对 Blueprint Action 使用 PoliciesSails 内置的 blueprint API 是使用普通 Sails action 实现的唯一区别在于 blueprint action 是**隐式implicit**的。要对 blueprint action 应用 policy只需像上面的例子一样设置 policy 映射只不过把目标指向控制器中相应隐式 blueprint action 的名称或作为独立 action。例如module.exports.policies { UserController: { // 将 isLoggedIn policy 应用于 UserController 的 update action update: isLoggedIn } };或module.exports.policies { user/update: isLoggedIn };全局 Policies你可以通过*属性把某个 policy 应用到所有未被显式映射的 action。例如module.exports.policies { *: isLoggedIn, user/login: true };这会把isLoggedInpolicy 应用到除api/controllers/user/login.js中的loginaction或api/controllers/UserController.js中的loginaction之外的所有 action。内置 PoliciesSails 提供了两个内置 policy可以全局应用也可以应用于特定控制器或 actiontrue公共访问允许任何人访问被映射的 controller/actionfalse禁止访问不允许任何人访问被映射的 controller/action*: true是所有控制器和 action 的默认 policy。在生产环境中好的做法是将其改为false以防止访问任何你可能无意暴露的逻辑。这两个内置值在源码中有明确的对应实现lib/hooks/policies/index.js 定义了neverAllow调用res.forbidden()和alwaysAllow直接调用next()两个中间件函数并分别标记为POLICY: false (neverAllow)与POLICY: true (alwaysAllow)同时true/false会先被包装成单元素数组且如果数组中还有其他 policy 会直接抛错E_INVALID_POLICY_CONFIG也就是说true/false必须是该目标唯一的策略。此外配置校验非常严格buildPolicyMappolicy 可以是字符串必须对应已加载的 policy 名称否则抛出E_INVALID_POLICY_CONFIG、函数或true/false其他任何类型都会在应用启动时直接报错终止。编写你的第一个 Policy下面是一个简单的isLoggedInpolicy用于阻止未认证用户访问。它检查 session 中是否有userId属性如果找不到就发送默认的forbidden响应。对许多应用来说这可能就是唯一需要的 policy。下面的例子假设在认证用户的控制器 action 中你把req.session.userId设置成了一个真值truthy。// api/policies/isLoggedIn.js module.exports async function (req, res, proceed) { // 如果设置了 req.me我们就知道这个请求来自已登录用户 // 因此可以安全地继续执行下一个 policy—— // 或者如果这是最后一个 policy则继续执行相应 action。 // 关于 req.me 从哪来请查看本应用的自定义 hookapi/hooks/custom/index.js。 if (req.me) { return proceed(); } //--• // 否则这个请求不是来自已登录用户。 return res.forbidden(); };Policy 的加载机制从源码看policy 文件的加载发生在 policies hook 的loadMiddleware()中lib/hooks/policies/index.js它调用sails.modules.loadPolicies()后者lib/hooks/moduleloader/index.js使用includeAll.optional()从sails.config.paths.policies即api/policies/目录加载所有非md/txt的模块并扁平化、保留目录结构。加载后如果通过编程方式在sails.config.policies.moduleDefinitions中提供了 policy 函数会覆盖从磁盘加载的同名 policy每个加载的 policy 都会被校验必须是函数否则抛出E_INVALID_POLICY错误每个 policy 函数会被打上_middlewareType POLICY: name标记便于日志和调试。在路由上直接绑定 Policy除了config/policies.js的声明式映射policies hook 还监听了route:typeUnknown事件lib/hooks/policies/index.js允许你在config/routes.js中通过policy: ...路由选项把 policy手动绑定到显式路由上。若引用的 policy 不存在会调用 errors/fatal.js 中的__UnknownPolicy__打印错误Unknown policy, xxx, referenced in ...并终止进程。进阶动态权限与 Helper 方案生成一个带示例的 Web App要查看访问控制的实际示例——以及登录、认证和密码恢复——请生成一个 starter web appsails new foo # 然后选择 Web App动态权限结合数据库的细粒度控制对于更复杂的权限方案比如请求方主体的访问权同时取决于他们是谁who they are和他们想做什么what theyre trying to do你需要引入数据库。虽然你也可以用 policy 实现这一点但通常更直接、更易维护的做法是使用 helper。例如你可以创建api/helpers/check-permissions.jsmodule.exports { friendlyName: Check permissions, description: Look up a user\s rights within a particular organization., inputs: { userId: { type: number, required: true }, orgId: { type: number, required: true } }, exits: { success: { outputFriendlyName: Rights, outputDescription: A users rights within an org., outputType: [string] }, orgNotFound: { description: No such organization exists. } }, fn: async function(inputs, exits) { var org await Organization.findOne(inputs.orgId) .populate(adminUsers, { id: inputs.userId }) .populate(regularUsers, { id: inputs.userId }); if (!org) { throw orgNotFound; } var rights []; if (org.regularUsers.length ! 0) { rights [basicAccess, inviteRegularUsers]; } else if (org.adminUsers.length ! 0) { rights [basicAccess, inviteRegularUsers, removeRegularUsers, inviteOrgAdmins]; } else if (org.owner inputs.userId) { rights [basicAccess, inviteRegularUsers, removeRegularUsers, inviteOrgAdmins, removeOrDemoteOrgAdmins]; } // ^^这里可以按你的需要做到任意简单或精细例如 // [basicAccess, inviteRegularUsers, inviteOrgAdmins, removeRegularUsers, removeOrDemoteOrgAdmins] return exits.success(rights); } };你的 action——例如api/controllers/demote-org-admin.js——可能长这样//… var rights await checkPermissions(this.req.session.userId, inputs.orgId) .intercept(orgNotFound, notFound); if (!_.contains(rights, removeOrDemoteOrgAdmins)) { throw forbidden; } await Organization.removeFromCollection(inputs.orgId, adminUsers, inputs.targetUserId); await Organization.addToCollection(inputs.orgId, regularUsers, inputs.targetUserId); return exits.success();注意请记住虽然我们在这里使用了checkPermissions(…,…)但我们也可以使用.with()切换到命名参数await checkPermissions.with({ userId: this.req.session.userId, orgId: inputs.orgId });你可以在不同场景中选择不同的 helper 调用方式来增强代码可读性。拿不定主意时一个很好的最佳实践是先追求显式explicitness再追求可读性readability最后才追求简洁conciseness。当然当你更频繁地使用某个 helper 并逐渐熟悉它之后这些优先级可能会发生变化。这种policy 做二元放行 action/helper 做细粒度业务授权的组合正是 Sails 官方推荐的权限架构policy 保持简单复杂判断下沉到可复用、可测试的 helper 与 action 中。小结与最佳实践清单Policies 只保护 action/controller不保护直接指向视图的路由——需要保护视图时先把它包进 action。保持 policy 简单二元判断是否登录、是否管理员放 policy细粒度、角色化权限放 action 或 helper。Policies 不级联更具体的映射会覆盖更宽泛的映射user/delete覆盖user/*user/*覆盖*多个 policy 用数组。利用内置值true放行、false拒绝二者不能与其他 policy 混用生产环境建议把全局默认从*: true改为*: false。利用源码机制排障policy 名必须与api/policies/下的文件名一致引用不存在的 policy 会导致启动失败Unknown policy。配置错误E_INVALID_POLICY_CONFIG同样会在启动时被拦截而不是在请求时才暴露。相关深入阅读Policies 概念文档、Access Control and Permissions、config/policies.js 解剖、策略 hook 源码、action middleware 注册、路由绑定与匹配。赞分享后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载相关推荐Kubernetes权威指南配置Kubelet API的RBAC授权机制Kubernetes权威指南配置Kubelet API的RBAC授权机制 引言为什么需要Kubelet API的精细授权控制 在Kubernetes集群中文档/教程Apache RocketMQ 权限控制ACL实战指南从配置部署到源码级原理解析Apache RocketMQ 权限控制ACL实战指南从配置部署到源码级原理解析 导读 Apache RocketMQ 的权限控制Access Cont消息队列后端微服务流处理Sails 之 config/local.js本地开发环境配置的权威指南Sails 之 config/local.js本地开发环境配置的权威指南 导读 config/local.js 是 Sails 应用中专门用于承载 个人本地环后端上一篇Zotero Style让文献管理变得高效实用的视觉化插件指南下一篇Byzer Notebook详解数据科学家的理想工作环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表