为什么我不能信任客户端生成的GUID?将主键(PK)视为由客户端GUID和服务器GUID组成的复合键是否解决了问题?

9
我在之前与Jon Skeet讨论的基础上构建。
我的方案大意如下:
客户端应用程序可以创建新的“PlaylistItem”对象,并将其持久化到数据库中。
使用情况要求以这种方式创建PlaylistItem,以便客户端在显示PlaylistItem之前无需等待服务器响应。
客户端生成一个UUID用于PlaylistItem,在客户端显示该PlaylistItem,然后向服务器发出保存命令。
现在,我明白在我的数据库中使用客户端生成的UUID作为对象的主键是不好的做法。原因是恶意用户可以修改生成的UUID,强制PK在我的数据库中发生冲突。
为了减轻由于强制PK冲突而造成的任何损失,我选择将PK定义为两个ID的组合 - 客户端生成的UUID和服务器生成的GUID。服务器生成的GUID是PlaylistItem的Playlist的ID。
现在,我已经使用这个解决方案有一段时间了,但我不明白为什么/相信我的解决方案比简单地信任客户端ID更好。如果用户能够强制使PK与另一个用户的PlaylistItem对象发生碰撞,那么我认为他们也可以提供该用户的PlaylistId。他们仍然可以强制碰撞。

所以...是的。像这样做的正确方式是什么?允许客户端创建UUID,服务器在成功保存时给出肯定/否定。如果发现碰撞,则恢复客户端更改并通知检测到碰撞?

2个回答

4

在服务器上,您可以信任客户端生成的UUID或类似的全局唯一标识符。只要合理使用它即可。

大多数表/集合也将持有userId或能够通过外键与userId关联。

如果您正在进行插入操作并且恶意用户使用现有密钥,则插入将失败,因为记录/文档已经存在。

如果您正在进行更新操作,则应验证已登录用户是否拥有该记录或是否被授权(例如管理员用户)对其进行更新。如果正在强制执行所有权(即无管理员用户场景),则用于定位记录/文档的where子句将包括Id和userId。现在从技术上讲,在where子句中添加userId是多余的,因为Id将唯一找到一个记录/文档。但是添加userId可以确保记录属于正在进行更新的用户而不是恶意用户。

我假设服务器解密了加密令牌或某种会话以确定userId,并且这不是由客户端提供的,否则显然不安全。


我也曾持有这种观点,直到发现我使用的文档数据库(CosmosDB)并没有强制实施唯一ID。也就是说,在逻辑分区内,“id”字段仅是唯一的。在我的情况下,使用客户端生成的UUID是很危险的。 - Peter L

3
一个好的解决方案可能是以下内容:引用Sam Newman的"Building Microservices"
调用系统将会POST一个BatchRequest,可能会传递一个位置,以便放置所有数据的文件。客户服务将返回HTTP 202响应代码,表示请求已被接受,但尚未被处理。调用系统可以轮询资源,等待直到检索到201 Created,表示请求已被满足。
因此,在您的情况下,您可以向服务器POST,但立即获得像“我将保存PlaylistItem,并承诺其ID将是这个”的响应。客户端(和用户)可以继续进行,而服务器(可能甚至不是API,而是从API收到消息的某个后台处理器)需要时间来处理、验证和执行其他可能的重要逻辑,直到保存实体。如前所述,API可以为该请求提供GET端点的状态,客户端可以轮询它,并在出现错误时采取相应措施。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接