RESTful API:如何安全地存储不记名 API 令牌?

RESTful API: how to securely store bearer API tokens?

我正在构建一个 RESTful API。我唯一的问题是如何进行身份验证,因为我想要一种无状态的方法,其中服务器拥有的唯一信息是请求本身。

所以我想我会看看大男孩是怎么做的。

我看到大多数服务都会发行 users/applications 令牌。然后在每个后续请求中使用它。例如 Twitter 和 GitHub 使用 OAuth2,我看到他们发布了不记名令牌。到目前为止,非常好 - 无状态、干净和简单:

$ curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/xyz

但是我有一个问题:我是否将 OAUTH-TOKEN 令牌存储在我的数据库中以验证用户...如果是,如何?


(编辑以澄清问题)

假设这是我的数据库 table:

用户 |代币
美国广播公司 | 123
坐标 | 789

第一个用户想要使用他们的令牌发出 API 请求。所以他们知道他们的令牌是“123”,所以他们这样做:

curl -H "Authorization: Bearer 123" https://myapi.com

这就是我的 API 必须继续的所有信息,所以它查找 WHERE token = "123",并找出它的用户 "abc"。简单的。都好。返回响应。

理想情况下,我希望我的 table 像那样(简单,无开销)所以我的问题实际上是:像那样将令牌存储在数据库中是个坏主意吗?

(我想这是因为我习惯于认为这很糟糕,只是因为处理正常的 email/password 行)

所以 然后 我想,好吧,假设我 do 需要在我的 table 中散列这些标记:如何然后我会查找该行吗?那就是你关于查找散列值的最后一个问题的来源:我假设有可能发生冲突,因为如果两个令牌具有相同的散列,那么如果你根据散列值查找 一个人你肯定不知道哪个用户发出了请求,对吗?

这让我了解如何添加如何识别行的附加值。就像您需要电子邮件和密码来标识一行一样——而不仅仅是密码——我想知道 API 请求的等效项是什么。但是,是的,最简单的解决方案是最好的,我认为简单地将它与令牌一起传递确实可以巧妙地解决问题。

您确实回答了 "how would I identify the row if I do need to store the tokens hashed" 问题。

剩下的唯一问题是"Do I even need to store them hashed - and incur that overhead?"

我没有看到这里的问题,所以我想我误解了你的问题。这是我想你问的,请纠正我错误的地方:

  1. 您对身份验证的唯一要求是它是无状态的。具体来说,使用 OAuth 不是要求。
  2. 身份验证用于您的 REST API。 API 令牌不能代表您的用户访问某些其他服务。

假设这是真的,那么您可以简单地在身份验证 header 中发送更多信息,而不仅仅是令牌。例如:

Authorization: MyScheme base64urlEncodedUserName.base64urlEncodedAccessToken

这将允许您根据用户名执行查找。

我也不明白为什么使用令牌作为密钥是个问题,即使您将其存储为哈希值。只是散列传入的令牌并根据散列值执行查找?


编辑:感谢您澄清问题,改进以下回复:

将未散列的访问令牌存储在数据库中是否不好?

是也不是。通过存储令牌而不是用户密码,您已经消除了将 he/she 可能已为多个站点重复使用的用户密码暴露给攻击者的危险。所以它绝对没有存储未散列的密码那么糟糕。

但它可能仍然很糟糕,这取决于令牌授予访问权限的信息或操作类型 - 如果它用于论坛软件之类的东西,那么它可能没问题。如果涉及到信用卡信息,那肯定是不好的。

问题本质上变成了:攻击者可以用访问令牌做什么,他(她)已经不能做什么,已经入侵了数据库?如果使用令牌唯一可用的信息已经存储在数据库中,并且不能使用令牌执行任何危险操作,那么对令牌进行哈希处理几乎无法获得额外的安全性。

散列令牌会导致冲突吗?

嗯,这实际上提出了一个有趣的观点。许多人使用普通的散列函数来散列他们的密码和盐。这可能会导致碰撞,是的。但是如果你散列你的令牌,你应该使用加密散列函数来这样做。在这种情况下,冲突的可能性非常低(至少如果令牌足够长),它可能会被忽略。

请参阅 Why passwords should be hashed and How to safely store a password 了解加密散列的一些不错的文章。