从UUID或HMAC / JWT / hash生成一次性安全令牌?

时间:2018-01-09 15:12:32

标签: security authentication hash jwt hmac

我为网络应用构建了后端。当新用户访问该网站并点击注册按钮时,他们会填写一个超级简单的表单,询问他们的用户名+密码,然后他们就会提交。这会提示服务器向该电子邮件地址发送验证电子邮件。然后,他们会检查他们的电子邮件,点击链接(验证他们的电子邮件),然后将其路由到登录页面,以便他们可以选择登录。

为了验证他们的电子邮件,当服务器生成电子邮件时,它需要创建(并存储)验证令牌(可能是UUID)并将其附加到电子邮件中的此链接,这样链接看起来像:

  

" https://api.myapp.example.com/v1/users/verify?vt=12345"

其中vt=12345是"验证令牌" (再次可能是UUID)。因此,用户单击此链接,我的GET v1/users/verify端点查看令牌,以某种方式确认其有效,并进行一些数据库更新以激活"用户。他们现在可以登录了。

当用户想要取消订阅接收电子邮件,或者他们无法记住密码并需要恢复以便他们可以登录时的类似情况。

取消

用户希望停止接收电子邮件,但仍想使用该应用。他们点击" 取消订阅"链接我们发送给他们的每周简报。此链接需要包含某种类似的"取消订阅令牌"与上述验证令牌一样,生成+存储在服务器上,用于验证用户取消订阅电子邮件的请求。

恢复密码

此处用户忘记了密码并需要恢复密码。因此,在登录屏幕上,他们会点击" 忘记密码"链接,并提供一个表格,他们必须填写他们的电子邮件地址。服务器向该地址发送电子邮件。他们检查此电子邮件,其中包含指向表单的链接,用户可以在其中输入新密码。此链接需要包含"重置密码令牌"它 - 就像上面的验证令牌一样 - 生成并存储在服务器上,用于验证用户更改密码的请求。

所以这里我们有三个非常相似的问题需要解决,所有这些都需要使用我所调用的" 一次性(OTO)安全令牌" 。这些OTO令牌:

  • 必须生成服务器端并持久化(可能是security_tokens表)
  • 必须是可以附加到我们将从电子邮件内部公开的链接的内容
  • 必须只有一次有效:一旦他们点击它,令牌就会被使用"并且不能重复使用

我的问题

我提出的解决方案很简单......几乎太简单了。

对于令牌,我只是生成随机UUID(36-char)并将它们存储到具有以下字段的security_tokens表中:

[security_tokens]
---
id (PK)
user_id (FK to [users] table)
token (the token itself)
status (UNCLAIMED or CLAIMED)
generated_on (DATETIME when created)

当服务器创建它们时,它们是#34; UNCLAIMED"。当用户点击表格内的链接时,他们就会被宣布"声明"。后台工作程序作业将定期运行以清理任何已发送的令牌或删除任何已过期的未处理的令牌#34; (基于他们的generated_on字段)。该应用程序还将忽略之前已声明(并且尚未清除)的任何令牌。

认为这个解决方案可行,但我不是一个超级安全人员,我担心这种方法:

  1. 可能会让我的应用程序对某种类型的攻击/攻击开放;和
  2. 当其他一些解决方案也可以正常工作时,可能会重新发明轮子
  3. 与上面的第二个一样,我想知道我是否应该使用哈希/ HMAC / JWT相关机制而不是死的简单UUID。也许有一些聪明的加密/安全人员找到了一种方法来使这些令牌以安全/不可变的方式包含CLAIM状态和到期日期等。

1 个答案:

答案 0 :(得分:2)

您排在第右边

我的应用程序中有一个非常类似的方法,基于我想要它做的事情。我有一个包含每个用户的表(一个Users表),我可以用它来引用每个帐户并根据他们的身份执行操作。通过添加用户帐户和自我管理选项可以缓解批次的安全威胁。以下是我如何对抗其中的一些漏洞。

验证您的电子邮件

当用户注册时,服务器应使用RNGCryptoServiceProvider()类生成一个足够长度的随机盐,以至于无法实际猜测。然后,我对盐(它自己)进行哈希处理并对其应用base64编码,以便将其添加到Url中。通过电子邮件将完成的链接发送给用户,并确保将该哈希值存储在UserId表中的相关Users

用户看到一个漂亮而整洁的"点击此处验证您的电子邮件地址"在他们的收件箱中,可以点击链接。它应该重定向到接受可选url参数的页面(例如mywebsite.com/account/verifyemail/myhash,然后检查哈希服务器端。然后,站点可以检查哈希与它存储在数据库中的激活哈希值。如果匹配记录,然后您应该将Users.EmailVerified列标记为true并提交到表。然后,您可以从表中删除该验证记录条目。

干得好,您已经成功验证了用户的电子邮件地址是真的!

重设密码

在这里,我们实现了一个类似的方法。但是,我们最好不要将记录存储在PasswordResetRequest表中,而不是删除记录 - 这样您就可以查看密码是否重置以及何时重置,而不是验证记录。每次用户请求重置密码时,您都应该显示匿名消息,例如"电子邮件已发送到您的主电子邮件地址,其中包含更多说明"。即使没有发送一个帐户或帐户也不存在,它也会阻止潜在的攻击者枚举用户名或电子邮件地址,以查看他们是否已在您的服务中注册。同样,如果它们是真实的,请使用与以前相同的方法发送链接。

用户打开他们的电子邮件地址并点击该链接。然后将它们重定向到重置页面,例如mywebsite.com/account/resetpassword/myhash。然后,服务器在URL中针对数据库运行哈希,并返回结果(如果它是真实的)。现在,这是一个棘手的部分 - 你不应该长期保持这些活跃。我建议将一个列连接到Users.UserId的列,一个名为ExpiraryDateTime的列,其中包含Datetime.Now.AddMinutes(15)(这使得以后更容易使用),以及一个名为IsUsed的列。 {1}}作为布尔值(默认为false)。

点击链接后,您应该检查链接是否存在。如果没有,请将它们提供给默认值&#34;该链接出现问题。请申请一个新的&#34;文本。但是,如果该链接有效,您应该检查Used == false,因为您不希望人们多次使用同一链接。如果不使用,太棒了!我们来检查一下它是否仍然有效。最简单的方法是简单的if (PasswordResetRequest.ExpiraryDateTime < DateTime.Now) - 如果链接仍然有效,那么您可以继续密码重置。如果没有,这意味着它是在不久前生成的,你不应该再让它使用了。说真的,有些网站仍然允许你今天生成一个链接,如果你的电子邮件在一个月后被黑了,你仍然可以使用重置链接!

我还应该提到,每次用户请求重置密码时,您都应该检查表中的现有记录,以获取有效链接。如果一个有效(意味着它仍然可以使用),那么你应该立即使它无效。将哈希替换为一些辅助文本,例如&#34;无效:用户请求新的重置链接&#34;。这也让您知道他们已经请求了多个链接,同时也使他们的链接无效。你也可以将它标记为已使用,如果你真的只是为了防止人们试图使用过期的链接,聪明并潜行整个&#34;无效:用户请求新的重置链接&#34;作为其浏览器的编码URL。您永远不应该为同一帐户激活多个重置链接 - 永远

<强>取消注册

为此,我在数据库中有一个简单的标志,用于确定用户是否可以接收促销优惠和简报等。因此Users.SubscribedToNewsletter就足够了。他们应该能够在他们的电子邮件设置或通信首选项等中登录并更改它。

一些代码示例

这是我在C#

中的RNGCryptoServiceProvider代码
public static string GenerateRandomString(RNGCryptoServiceProvider rng, int size)
{
    var bytes = new Byte[size];

    rng.GetBytes(bytes);

    return Convert.ToBase64String(bytes);
}

var rng = new RNGCryptoServiceProvider();
var randomString = GenerateRandomSalt(rng, 47); // This will end up being a string of almost entirely random bytes

为什么我要使用RNGCryptoServiceProvider?

RNGCryptoServiceProvider()(它的安全库中的C#类)允许您基于完全随机和不可再现的事件生成看似随机的字节串。像Random()这样的类仍然需要使用某种内部数据来生成基于可预测算法事件(例如当前日期和时间)的数字。 RNGCryptoServiceProvider()使用诸如cpu温度,运行进程数等所有内容来创建无法再现的随机内容。这允许最终的字节数组尽可能随机。

为什么我对Base64进行编码?

Base64编码将生成仅包含数字和字母的字符串。这意味着文本中不会有符号或编码字符,因此在URL中使用是安全的。这不是一个安全功能,但它允许您只允许方法参数中的数字和字母,并过滤掉或拒绝任何不符合此标准的输入。例如,过滤掉包含V形<>的任何输入应该可以阻止XSS。

要记住的事情

您应该始终假设包含您的哈希的链接无效,直到您对其执行每次检查以确保它通过要求为止。因此,您可以执行各种if语句,但除非您通过每一个,否则会将默认的下一个操作保留为用户的某种形式的错误。为了澄清,我应该检查密码重置链接是否有效,然后不使用,然后仍然在时间窗口内,然后执行我的重置操作。如果它未能通过任何这些要求,则默认操作应该是向用户提供错误,指出它是无效链接。

其他人注意事项

由于我非常有信心这不是唯一的方式,我只是想宣布这就是我做的事情。它多年来一直没有让我失望,并通过几个广泛的测试使我的公司。但是,如果某人有更好/更安全的方式,请尽量了解一下,因为我很乐意了解更多信息。如果您对我提到的特定部分有任何进一步的问题或需要澄清,请告诉我,我会尽力帮助