
请求里带着tenant_id,并不意味着它来自这个租户。合法令牌也不意味着可以查询该租户的任意订单。身份、资源与对象权限需要连接起来。
从可信主体向内约束参数
MCP授权规范要求令牌用于正确的资源;OAuth2.1在该版本中引用的仍是IETF草案,不能把它写成已发布RFC。业务网关得到验证后的主体后,再把主体所属租户与对象所属租户比较。
模型可以提出查询对象,但不应替换可信上下文里的租户。只检查请求参数与令牌里某个字符串相同,也不足以证明数据库对象确实归属该租户。对象归属应由后端读取。
富授权请求可以表达更细权限,表达能力却不会自动变成执行效果。每个约束都应有明确的服务端检查点,包括字段集合、查询上限和对象级授权。

图中身份、租户和对象三道门都需要通过;红色跨租户路径没有“改一下参数再试”的出口。
把对象归属加入授权条件
以下是可离线运行的最小示例,使用Python标准库。输入均为教学构造,不连接业务系统。
def authorize(principal, obj, args):
if principal.get("aud") != "orders-service":
return "DENY_AUDIENCE"
if principal.get("tenant") != obj.get("tenant"):
return "DENY_TENANT"
if set(args) != {"limit"}:
return "DENY_FIELDS"
n = args["limit"]
if type(n) is not int or not 1 <= n <= 20:
return "DENY_LIMIT"
return "ALLOW"
p = {"aud": "orders-service", "tenant": "a"}
o = {"tenant": "a"}
assert authorize(p, o, {"limit": 5}) == "ALLOW"
assert authorize(p, {"tenant": "b"}, {"limit": 5}) == "DENY_TENANT"
assert authorize(p, o, {"limit": True}) == "DENY_LIMIT"
assert authorize(p, o, {"limit": 5, "tenant": "b"}) == "DENY_FIELDS"
print("4 object authorization cases passed")
principal在此是假定已完成签名、发行者、受众和期限验证的可信对象。脚本没有解码JWT,没有实现OIDC或RAR协议。
缓存键也属于权限模型
缓存不能只按工具名和参数命中,还要考虑主体、租户、授权版本及对象变化。权限撤销后旧缓存是否仍能返回数据,需要专门的反例。
本例只验证参数和对象关系;没有真实令牌、授权服务器、数据库或撤销传播测试。
本文阐述MCP参数级授权的核心原则:租户身份不可由调用参数自报,必须通过可信令牌与后端对象归属校验双重确认。强调OAuth2.1草案在授权链中的定位、对象级授权的必要性、多租户隔离的强制检查点,以及缓存键需纳入权限模型(含主体、租户、授权版本)。所有授权约束须有服务端明确验证,杜绝跨租户越权访问。

280

被折叠的 条评论
为什么被折叠?



