一个SaaS后台里,管理员能看到哪些菜单、普通用户能不能看到“删除”按钮,看起来像是前端UI的问题,实际上只是权限系统最外层的一小部分。真正决定系统是否安全的,是用户发起请求以后,服务器如何确认身份、判断权限、确定数据所属租户,再根据业务规则判断这次操作是否允许执行。
因此,SaaS权限控制不能建立在“按钮隐藏”之上。前端可以负责改善用户体验,但不能承担安全责任。只要用户能够直接构造HTTP请求,服务器就必须重新完成身份认证、授权判断、数据范围校验和业务规则校验。
理解这一点,可以先把几个经常混在一起的概念分开。Authentication,也就是认证,回答的是“你是谁”;Authorization,也就是授权,回答的是“你可以做什么”;Data Scope,也就是数据范围,回答的是“你可以对哪些数据做”;Business Rules,则回答的是“即使你拥有这个权限,现在这个操作是否符合业务规则”。
这四个问题如果只解决其中一个,权限系统依然可能存在漏洞。
例如一个用户拥有edit_user权限,只能说明他被允许执行“编辑用户”这一类操作,并不意味着他可以编辑系统中的任何用户。如果这是一个多租户SaaS,他还必须属于对应tenant,并且可能只能编辑自己创建的用户,或者只能编辑自己部门负责的用户。
因此,一个API请求不能只检查“有没有edit_user”。
服务器首先需要确认请求携带的身份凭证是否有效,然后确定当前用户属于哪个租户,再确定该用户拥有的角色和权限,接着根据请求中的资源ID查询目标对象属于哪个tenant以及用户是否有权访问该对象,最后再执行业务规则判断。
这条链路可以概括为:
用户身份 → 租户归属 → 权限判断 → 数据范围 → 业务规则 → 执行操作
其中任何一个环节缺失,都可能产生越权问题。
RBAC,也就是基于角色的访问控制,是SaaS后台最常见的权限模型之一。系统不是给每个用户直接配置几十甚至几百项权限,而是先定义角色,例如Owner、Admin、Manager、Editor和Viewer,再把权限分配给角色,用户通过角色获得对应权限。
例如invoice.read、invoice.create、invoice.update、invoice.delete可以组成发票模块的权限集合。一个Editor可能拥有读取和编辑权限,而Viewer只有读取权限。
RBAC解决的是“用户可以执行什么操作”,但它没有自动解决“可以操作哪些记录”。
这就是数据范围控制。
假设一个企业SaaS中存在100万条客户记录,用户A拥有customer.read权限。如果API只判断这个权限,然后执行SELECT * FROM customers,那么用户A可能获得整个系统的客户数据。权限名称本身并不能限制数据范围。
因此,查询通常还必须带上租户和授权范围条件。例如:
SELECT * FROM customers WHERE tenant_id = ?
如果业务要求用户只能访问自己创建的数据,还需要进一步增加:
AND owner_id = ?
如果权限范围是部门级,则可能还需要根据用户所属部门过滤数据。
这也是多租户SaaS最重要的安全原则之一:tenant_id不能只是一个普通业务字段,而必须成为数据访问边界的一部分。
应用程序可以在Repository、Service或者DAO层统一加入租户过滤,也可以在数据库层采用Row-Level Security等机制进一步限制行级访问。具体采用哪一种方式取决于数据库、架构和团队的运维能力。数据库级安全策略可以形成额外防线,但不能因为使用了RLS或者数据库视图,就认为应用层授权已经不重要。
API层是另一个核心位置。
前端发出的请求最终都会变成HTTP请求,因此任何用户都可以使用浏览器开发者工具、curl或者其他HTTP客户端重新构造请求。服务器不能相信“这个按钮只有管理员才能看到”这种前端状态。
例如后台页面隐藏了删除用户按钮,但用户仍然可以手动发送:
DELETE /api/users/123
服务器必须独立判断当前用户是否拥有删除权限,以及用户123是否属于当前租户和当前用户能够管理的数据范围。
如果没有这一步,前端所谓的权限控制基本只是界面设计。
JWT也经常在这里被误解。JWT可以携带用户身份和部分声明,但JWT本身并不是RBAC,也不是完整的授权系统。服务器不能因为JWT里面出现了role=admin就无条件允许所有管理操作。
尤其需要考虑权限变化问题。如果用户登录以后被管理员取消权限,而系统长期依赖已经签发的JWT,那么旧令牌在有效期内可能仍然包含原来的权限信息。因此,权限缓存、令牌生命周期、撤销机制以及敏感操作的重新验证,都需要根据具体系统设计。
OAuth 2.0同样不能被简单称为“权限控制”。OAuth 2.0主要解决的是授权委托问题,例如一个应用是否可以代表用户访问某个资源。具体SaaS内部的角色、数据范围和业务授权,仍然需要应用自己的授权模型。
业务逻辑层解决的是另一类问题:用户虽然拥有权限,但当前操作是否符合业务规则。
例如一个财务SaaS允许Manager修改发票,那么invoice.update只能说明Manager具有编辑发票的权限。如果发票已经进入付款流程,系统可能规定任何普通Manager都不能修改金额;或者修改金额超过某个额度必须经过审批。
此时系统需要判断的已经不是简单的权限,而是业务状态。
例如:
发票必须处于Draft状态;
当前用户必须属于该发票所属tenant;
当前用户必须拥有invoice.update权限;
当前用户必须属于该部门的管理范围;
金额修改超过指定额度时必须经过审批。
这些条件全部满足以后,系统才能执行更新。
这也是为什么授权逻辑和业务逻辑不能完全混为一谈。权限系统负责判断“你有没有资格做这件事”,业务服务则负责判断“这件事在当前状态下能不能做”。
前端权限控制仍然有价值,但它的作用主要是用户体验。
如果Viewer没有删除权限,前端可以不显示Delete按钮;如果用户没有访问Billing模块的权限,可以不显示Billing菜单。这样可以减少误操作,也可以让后台界面更加清晰。
但这些判断必须在后端重复执行。
前端隐藏按钮可以让用户看不到操作入口,却不能阻止用户直接调用API。后端授权则决定请求最终能不能改变数据。
所以,一个成熟SaaS通常会形成“前端隐藏 + API授权 + 数据范围 + 业务校验”的组合,而不是把安全责任压在前端。
多租户隔离又会让权限系统增加一个非常重要的维度。
假设两个企业A和B同时使用同一个SaaS。A公司的管理员即使拥有customer.read和customer.update,也只能操作A公司的客户数据。B公司的数据不能因为ID连续、接口参数可猜测或者数据库查询缺少过滤而被读取。
例如A公司的客户ID是10001,用户修改请求:
PUT /api/customers/10001
服务器不能只根据customer_id=10001找到记录然后更新,而应该先确认这条记录属于当前tenant。
正确的逻辑更接近:
SELECT ... FROM customers WHERE id = ? AND tenant_id = ?
这样即使用户知道其他租户的资源ID,也无法通过简单修改URL或者JSON参数访问其他企业的数据。
这种问题在安全测试中非常常见,通常被归入Broken Access Control一类。很多越权漏洞并不是密码验证失败,而是用户已经成功登录之后,系统没有正确判断他能访问哪些资源。
权限粒度也需要控制。
权限过粗,例如只设置一个admin和一个user,系统初期开发很简单,但业务增长以后容易出现大量特殊情况。权限过细,则会产生数百甚至数千个难以维护的权限项。
比较实际的做法,是围绕业务资源和操作设计权限,例如:
user.read
user.create
user.update
user.delete
invoice.read
invoice.update
invoice.approve
然后再通过角色和数据范围组合这些权限。
对于特殊业务,再增加条件授权。例如只有财务经理可以审批金额超过某个阈值的付款,或者只有资源负责人能够修改某类记录。
这样比给每一个用户单独维护一套权限更加容易管理。
权限缓存也是生产环境必须考虑的问题。
如果每一个API请求都查询数据库获取用户角色和权限,系统规模扩大以后会增加数据库压力。因此很多SaaS会把权限数据缓存到Redis或者应用缓存中。
但缓存不能成为权限事实来源。
例如管理员刚刚撤销某个用户的权限,而缓存仍然保存旧角色。如果缓存有效期设置过长,用户可能在一段时间内继续执行已经不允许的操作。
因此需要根据风险选择缓存策略。普通页面访问可以接受较短的缓存时间,而删除账户、修改付款信息、改变管理员权限等高风险操作可能需要更严格的实时授权检查。
同时,权限更新应该具有明确的失效机制。角色变化以后,需要让相关缓存及时失效,或者通过版本号、权限版本、session版本等机制让旧授权状态失效。
SaaS权限系统还必须考虑审计。
对于普通读取操作,系统不一定需要记录每一次数据库查询,但对于删除用户、修改管理员权限、改变账单、审批付款、导出大量客户数据等敏感操作,应记录操作者、tenant、目标资源、操作时间、结果以及必要的请求上下文。
审计日志的价值不仅是追查攻击。很多企业客户购买SaaS以后,本身就需要知道谁修改了什么数据。权限系统因此同时承担安全控制和业务治理功能。
从架构角度看,可以把一个典型SaaS后台的授权链路设计成这样:
用户登录以后获得身份凭证。API Gateway或者应用入口验证凭证。应用确定用户身份和tenant。授权模块判断用户角色和操作权限。数据访问层根据tenant和数据范围过滤资源。业务Service检查当前资源状态和业务规则。所有条件满足以后才执行数据库写入。敏感操作再进入审计日志。
这个过程不要求每一层都复制一套完整的RBAC系统,而是要求每一层承担自己应该承担的安全责任。
认证系统负责证明身份,授权系统负责判断权限,数据访问层负责防止跨租户和越范围访问,业务服务负责状态和规则,前端负责界面呈现,审计系统负责记录关键行为。
这样的设计也比单纯讨论“数据层、接口层、前端层谁是第一道防线”更加符合实际SaaS工程。
一个成熟的权限系统最终追求的并不是权限配置越复杂越好,而是让每一次敏感操作都能够回答几个明确的问题:是谁发起的请求,属于哪个tenant,拥有哪个权限,访问的是哪条数据,是否属于允许的数据范围,当前业务状态是否允许执行,以及整个操作是否留下了足够的审计记录。
只要这些问题能够在服务器端得到可靠回答,SaaS权限体系就有了比较清晰的安全边界。前端按钮只是让用户看到什么,JWT只是传递身份和声明,RBAC只是组织权限的一种方式,数据库只是整个防护体系中的一个环节。最终决定系统能否抵御越权访问的,是认证、授权、租户隔离、数据范围控制和业务规则能否形成一条完整而连续的验证链路。
SaaS 后台为什么不能只隐藏按钮,真正的权限控制应该在哪里完成
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP