生成唯一且安全的标识符以供“有时离线” Web应用程序使用的策略


47

我有一个基于Web的项目,允许用户联机和脱机工作,我正在寻找一种为客户端记录生成唯一ID的方法。我想要一种方法,该方法在用户脱机时(即无法与服务器对话)有效,并且保证是唯一的并且是安全的。通过“安全”,我特别担心客户端(恶意或以其他方式)提交重复的ID,从而严重破坏数据完整性。

我一直在做一些谷歌搜索,希望这已经解决了。我还没有找到任何非常确定的东西,尤其是在生产系统中使用的方法方面。我发现了一些系统的示例,在这些系统中,用户将仅访问他们创建的数据(例如,在多个设备上访问的Todo列表,但仅由创建它的用户访问)。不幸的是,我需要一些更复杂的东西。我确实在这里找到了一些非常好的想法,这些想法与我一直认为事情可能有效的方式是一致的。

以下是我建议的解决方案。

一些要求

  1. ID应该是全局唯一的(或至少在系统中唯一)
  2. 在客户端上生成(即通过浏览器中的javascript)
  3. 安全(如上所述和其他方面)
  4. 数据可以由多个用户查看/编辑,包括未编写数据的用户
  5. 不会对后端数据库(例如MongoDB或CouchDB)造成严重的性能问题

拟议的解决方案

当用户创建帐户时,将为他们提供一个uuid,该uuid由服务器生成,并且在系统内是唯一的。此ID不得与用户身份验证令牌相同。我们将此ID称为用户的“ ID令牌”。

当用户创建新记录时,他们会在javascript中生成新的uuid(如果可用,则使用window.crypto生成。请参见此处的示例)。该ID与用户创建帐户时收到的“ ID令牌”并置。现在,此新的复合ID(服务器端ID令牌+客户端uuid)是记录的唯一标识符。当用户在线并将此新记录提交到后端服务器时,该服务器将:

  1. 将其标识为“插入”操作(即不是更新或删除)
  2. 验证组合键的两个部分都是有效的uuid
  3. 验证所提供的复合ID的“ ID令牌”部分对于当前用户是否正确(即,它与服务器在创建用户时分配给该用户的ID令牌相匹配)
  4. 如果一切copasetic,插入数据到数据库(小心做插入,而不是一个“更新插入”,这样如果ID 确实已经存在,它不会更新错误现有记录)

查询,更新和删除不需要任何特殊逻辑。他们将简单地以与传统应用程序相同的方式将id用于记录。

这种方法的优点是什么?

  1. 客户端代码可以在脱机时创建新数据,并立即知道该记录的ID。我考虑了替代方法,即在客户端上生成一个临时ID,然后在系统联机时将其替换为“最终” ID。但是,这感觉很脆弱。特别是当您开始考虑使用外键创建子数据时,也需要对其进行更新。更不用说处理ID更改后会更改的网址。

  2. 通过将ID组合为客户端生成的值和服务器生成的值,每个用户都可以在沙箱中有效地创建ID。这旨在限制恶意/流氓客户端可能造成的损害。同样,任何id冲突都是基于每个用户的,而不是整个系统的全局冲突。

  3. 由于用户ID令牌与他们的帐户相关联,因此ID仅可以由经过身份验证的客户端(即用户成功登录的地方)在用户沙箱中生成。这旨在防止恶意客户端为用户创建错误的ID。当然,如果用户的身份验证令牌被恶意客户端窃取,他们可能会做坏事。但是,一旦身份验证令牌被盗,该帐户便会受到威胁。如果确实发生了这种情况,那么所造成的损害将仅限于被盗用的帐户(而不是整个系统)。

顾虑

这是我对这种方法的一些担忧

  1. 这样会为大型应用程序生成足够唯一的ID吗?有没有理由认为这会导致id冲突?javascript可以生成足够随机的uuid使其正常工作吗?看起来window.crypto 相当广泛,该项目已经需要相当现代的浏览器。(此问题现在有一个单独的SO问题

  2. 我是否缺少任何漏洞,这些漏洞可能允许恶意用户破坏系统?

  3. 查询由2个uuid组成的组合键时,是否有理由担心数据库性能。应该如何存储此ID以获得最佳性能?两个单独的字段还是一个对象字段?Mongo和Couch会有不同的“最佳”方法吗?我知道在执行插入操作时,使用非顺序主键会导致明显的性能问题。为主键自动生成一个值并将该ID存储为单独的字段会更聪明吗?(此问题现在有一个单独的SO问题

  4. 使用这种策略,可以很容易地确定一组特定记录是由同一用户创建的(因为它们都共享相同的公共可见id令牌)。虽然我看不到有任何立即出现的问题,但最好不要泄露比内部需求更多的信息。另一种可能性是对复合键进行哈希处理,但这似乎比其价值更大。

  5. 如果用户发生ID冲突,则没有简单的恢复方法。我想客户端可以生成一个新的id,但这似乎确实是不应该发生的。我打算解决这个问题。

  6. 只有经过身份验证的用户才能查看和/或编辑数据。这是我的系统可接受的限制。

结论

以上是合理的计划吗?我意识到其中一些归结为基于对相关应用程序的更全面了解的判断调用。


我认为这个问题可能interess你stackoverflow.com/questions/105034/... 也该读对我来说,GUID,但他们似乎并没有被JavaScript本地
雷米

2
令我惊讶的是,UUID已满足列出的5个要求。为什么它们不足?
加布2014年

@Gabe查看我对lie ryans的评论,以下内容
herbrandson 2014年


“恶意/恶意客户”-流氓。
戴维·康拉德

Answers:


4

您的方法将起作用。许多文档管理系统都使用这种方法。

要考虑的一件事是,您不需要将用户uuid和随机项ID都用作字符串的一部分。您可以替代地散列两者的含义。这将为您提供一个较短的标识符,并可能带来一些其他好处,因为生成的ID将更加平均地分布(对于索引和文件存储,如果您是基于uuid来存储文件,则可以更好地平衡)。

您拥有的另一个选择是为每个项目仅生成一个临时uuid。然后,当您完成连接并将它们发布到服务器时,服务器会为每个项目生成(保证)uuid,并将其返回给您。然后,您更新本地副本。


2
我曾考虑过使用2的哈希作为id。但是,在我看来,没有一种合适的方法可以在我需要支持的所有浏览器中生成sha256 :(
herbrandson 2014年

12

您需要将两个问题分开:

  1. ID生成:客户端必须能够在分布式系统中生成唯一的标识符
  2. 安全问题:客户端必须具有有效的用户身份验证令牌,并且身份验证令牌对正在创建/修改的对象有效

不幸的是,这两个解决方案是分开的。但幸运的是它们并不兼容。

通过使用UUID生成ID,可以轻松解决ID生成的问题;但是出于安全考虑,您需要在服务器中检查给定的身份验证令牌是否已获得该操作的授权(即,如果auth令牌用于的用户不具有对该特定对象的必要权限,则必须被拒绝)。

如果处理正确,则冲突不会真正造成安全问题(仅要求用户或客户端使用另一个UUID重试该操作)。


这是一个很好的观点。也许这就是所需要的,而我对此却考虑过。但是,我对此方法有一些担忧。最大的问题是,JavaScript生成的uuid似乎不像人们希望的那样随机(请参见stackoverflow.com/a/2117523/13181上的注释以获取保留信息)。似乎window.crypto应该可以解决此问题,但似乎并非在我需要支持的所有浏览器版本中都可用。
herbrandson 2014年

继续...我喜欢您的建议,在客户端中添加代码,以在发生冲突时重新生成新的uuid。但是,在我看来,这重新介绍了我在帖子中“此方法的优点是什么”部分的第一点所关注的问题。我认为,如果我走这条路线,最好先在客户端生成临时ID,然后在连接服务器后使用服务器的“最终ID”更新它们
herbrandson 2014年

再次继续...此外,允许用户提交自己的唯一ID似乎是出于安全考虑。uuid的大小和碰撞的高度统计可能性不可能足以缓解这种担忧。我不确定。我有一个令人na惑的怀疑,在这种情况下,将每个用户保留在自己的“沙盒”中只是个好主意(即,不信任任何用户输入)。
herbrandson 2014年

@herbrandson:只要您始终检查用户是否具有该操作的权限,我就可以考虑允许用户生成自己的唯一ID的安全问题。ID只是可以用来识别对象的东西,它的值到底是什么并不重要。唯一的潜在危害是用户可以保留一定范围的ID供自己使用,但这对整个系统实际上并没有造成任何问题,因为其他用户不太可能仅凭偶然获得这些值。
Lie Ryan

感谢您的反馈意见。这真的迫使我澄清自己的想法!我对您的方法保持警惕是有原因的,并且我一路上已经忘记了它:)。我的担心与许多浏览器中糟糕的RNG有关。对于uuid生成,人们会更喜欢具有加密功能的RNG。许多较新的浏览器都通过window.crypto具有此功能,而较旧的浏览器则没有。因此,恶意用户有可能找出另一个用户RNG的种子,从而知道将要生成的下一个uuid。这是感觉它可能会受到攻击的部分。
herbrandson 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.