diff --git a/_drafts/Article/Translation/how-i-would-do-auth.md b/_drafts/Article/Translation/how-i-would-do-auth.md index 34de183..9a91494 100644 --- a/_drafts/Article/Translation/how-i-would-do-auth.md +++ b/_drafts/Article/Translation/how-i-would-do-auth.md @@ -1,5 +1,5 @@ --- -title: How I would do auth +title: 我会如何实现身份验证 date: 2025-03-10T03:29:26.732Z authorURL: "" originalURL: https://pilcrowonpaper.com/blog/how-i-would-do-auth/ @@ -7,10 +7,101 @@ translator: "" reviewer: "" --- -Copyright © pilcrowonpaper +这是一篇关于我如何为面向公众的应用实现身份验证的简短文章。这不会是太深入或权威的内容 - 只是我当前观点的集合。不过,对一些人来说,它可能作为入门指南很有用。 -<!-- more --> +首先,如果应用是为开发者设计的,而我需要一些非常快速的东西,我会直接使用 GitHub OAuth。10 分钟内搞定。 -[Source code][1] +现在进入主要部分 - 我会如何实现基于密码的身份验证?对我来说,最低要求是密码加上使用认证器应用的双因素认证(2FA)。密钥通行证(Passkeys)还不够普及,而我只是觉得魔术链接(magic-links)很烦人。 -[1]: https://github.com/pilcrowonpaper/pilcrow \ No newline at end of file +> 始终实现速率限制,即使是非常基础的实现! + +会话管理 +------------------ + +100%使用数据库会话。我真的非常不喜欢 JWT,大多数情况下它们不应该被用作会话。 + +假设我只需要处理已认证的会话,我首选的方法是 30 天过期,但每次使用会话时都会延长过期时间。这确保活跃用户保持认证状态,而不活跃用户则会被登出。 + +注册 +------------ + +有争议的观点 - 我认为应用分享电子邮件是否已存在于系统中是可以的。如果电子邮件已被占用,只需告诉用户他们已经有一个账户。显著更好的用户体验,安全性损失很小。如果你不喜欢这样,就不要使用电子邮件进行身份验证。 + +无论如何,比防止用户枚举更重要的是检查密码是否在之前的泄露中出现过。[`haveibeenpwned.com`][1] API 可能是这方面的最佳选择。这将减少凭证填充攻击的有效性,即攻击者使用从其他网站泄露的密码来攻击账户。 + +密码使用 Argon2id 或 Scrypt 进行哈希处理 - 它们都足够好。Bcrypt 也可以,但不幸的是它有 50-70 个字符的限制。 + +速率限制将设置为每个 IP 地址每秒约 1 次尝试。如果开始收到垃圾信息,则使用验证码。 + +### 电子邮件验证 + +首先,我不会费心使用那些 100 个字符长的正则表达式。这是你唯一需要的电子邮件正则表达式: + +```regex +^.+@.+\..+$ +``` + +我还会检查电子邮件是否以空格开头或结尾,以确保用户没有输错。 + +对于电子邮件验证,我个人更喜欢一次性密码(OTP)而不是链接,但两者都可以。对于 OTP,每个账户每小时 5-10 次尝试的基本限制应该足够了。验证码将在 10 分钟,最多 15 分钟内有效。对于验证链接,我会将过期时间设置为 2 小时。 + +以下是我生成这些 OTP 的一些方法: + +```golang +bytes := make([]byte, 5) +rand.Read(bytes) +// 8个字符,40位熵 +// 我可能会使用自定义字符集来移除1、I、0和O。 +otp := base32.StdEncoding.EncodeToString(bytes) +``` + +```golang +// 8个字符,熵相当于~26位 +// 这引入了微小的偏差。 +// 参见RFC 4226了解为什么这样做没问题。 +bytes := make([]byte, 4) +rand.Read(bytes) +num := int(binary.BigEndian.Uint32(bytes) % 100000000) +otp := fmt.Sprintf("%08d", num) +``` + +登录 +----- + +同样,如果电子邮件无效,只需返回"账户不存在"。 + +登录限制将基于电子邮件。增加超时时间直到 5-10 分钟(例如 1、2、4、8、15、30、60、120、300 秒)。你不希望时间更长,以防止攻击者通过故意失败来阻止合法尝试。基于 IP 地址的速率限制将设置为每个 IP 每秒 1 次尝试。多个用户可以共享 IP 地址,所以我不想在这里太严格。无论如何,我对登录限制不太担心,因为我们在注册期间检查密码强度,并且我们启用了 2FA。 + +如果我真的担心账户锁定,我可能会考虑实现[设备 cookie][2],尽管如果我处理这类攻击,我可能需要监控请求并手动阻止请求。 + +2FA +--- + +对我来说,2FA 是必须的。应该始终提供通过认证器应用(TOTP)的 2FA。它对用户来说相对容易使用,对我来说也容易实现。密钥通行证和安全密钥将是我的下一个优先事项(注册密钥通行证的用户也应该被允许使用它们代替密码+2FA)。另一方面,SMS 成本高且容易受到 SIM 卡交换攻击。我会避免使用它。反正没人喜欢它们。 + +对于 TOTP,同样每个账户每小时 5-10 次尝试的基本限制应该足够了。对于密钥通行证,可能是每个 IP 地址每秒 1 次尝试。暴力破解密钥通行证是不可能的,但验证签名在某种程度上是资源密集型的,因此可能容易受到 DoS 攻击。 + +> 密钥通行证可以用作第二因素,也可以作为密码的替代品,但安全密钥应该只用作第二因素。 + +可选的,但如果我要实现恢复码,我会生成 5 或 10 个字节并进行 base32 编码,就像我在电子邮件验证部分展示的那样。使用 Argon2id/Scrypt 对它们进行哈希处理,并给用户随时重新生成它们的选项。它将是一次性使用的,使用它将断开与账户关联的所有 2FA 方法。我在这里会对尝试次数相当严格,设置为每个账户每小时或甚至每天 5 次尝试。 + +最后,用户应该使用他们的第二因素之一进行身份验证,然后才能编辑他们的 2FA 方法。这对用户来说会开始变得烦人,所以我只会每小时(或更少)要求一次,通过在会话中存储他们上次使用 2FA 的时间。 + +密码重置 +-------------- + +同样,我不介意告诉用户电子邮件是否有效。 + +登录限制和速率限制将与登录非常相似,并将基于电子邮件和 IP 地址。必要时添加验证码。 + +一次性 OTP 和链接都可以工作,它们的过期时间将类似于电子邮件验证。我会对代码或令牌进行哈希处理以确保安全,特别是因为这并不难。 + +即使是密码重置,也应该需要 2FA。 + +我遗漏了什么吗? +-------------------- + +如果有任何我应该添加到文章中的内容,请在 Twitter 或 Discord 上告诉我! + +[1]: https://haveibeenpwned.com/ +[2]: https://owasp.org/www-community/Slow_Down_Online_Guessing_Attacks_with_Device_Cookies