NOTE

2.7 如何设计用户注册登录功能

用户注册与登录的历史学习笔记,覆盖登录态、用户名密码、验证码与第三方登录的基本设计思路。

系统设计创建于 更新于 约 2 分钟读完historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 什么是登陆态

登录后,系统会返回用于标识用户身份的登录凭证,并在后续请求中据此识别用户。 不同场景可以使用不同权限范围和有效期的凭证。具体票据类型、命名、权限关系和有效期属于实现细节,这里只保留抽象概念。

2. 用户名+密码

2.1. 安全要求

首先保障数据很难被拖库。 即使数据被拖库,攻击者也无法从中破解出用户的密码 即使数据被拖库,攻击者也无法伪造登录请求通过验证。 即使数据被拖库,攻击者劫持了用户的请求数据,也无法破解出用户的密码

2.2. 注册流程

  1. 用户输入明文密码
    • password = 123456
  2. 客户端对密码进行普通Hash
    • client_hash = MD5(password) // e10adc3949ba59abbe56e057f20f883e
    • 如果攻击者截获了请求,那么可以知道明文密码,所以这步的作用在于避免传输明文密码
  3. 客户端对2的结果加固定盐进行慢Hash
    • client_hash = BCrypt(MD5(password) + salt) // MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2
    • 加salt的作用在于如果password很短,那么可以反推出password,加了salt之后再进行Hash就很难反推了
    • 即使不能反推,那么可以用正推(彩虹表暴力穷举)出password,所以这里使用的BCrypt而不是MD5的原因在于,BCrypt是个很慢的Hash函数,这样暴力穷举时就需要花费很长的时间
  4. 客户端传递3的结果给服务端
  5. 服务端生成随机盐
    SecureRandom random = new SecureRandom();
    byte server_salt[] = new byte[36];
    random.nextBytes(server_salt);   // tq2pdxrblkbgp8vt8kbdpmzdh1w8bex
  6. 服务端对第4步客户端传过来的Hash值+第5步随机生成的盐进行Hash
    server_hash = SHA256(client_hash + server_salt);  // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67
    • 这步的作用在于防止拖库后泄露密码
  7. 服务端把第6步的结果+第5步随机生成的盐存储到数据库中
    DB.save(server_hash, server_salt);

2.3. 登录流程

  1. 用户输入明文密码
    1. password = 123456
  2. 客户端对密码进行普通Hash
    1. client_hash = MD5(password) // e10adc3949ba59abbe56e057f20f883e
  3. 客户端对2的结果加固定盐进行慢Hash
    1. client_hash = BCrypt(MD5(password) + salt) // MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2
  4. 客户端传递3的结果给服务端
  5. 服务端取出数据库中该用户的Hash值和盐值
  6. 服务端对第4步的结果+第5步的盐值进行Hash得出结果
    result = SHA256(authentication_hash + server_salt);  // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67
  7. 对比第6步计算结果和第5步取出的Hash值是否相同

3. 手机号+验证码

登录.md(关联笔记尚未公开)

4. 第三方登录

通过OAuth.md拿到第三方的OpenID,再把OpenID和自己的登陆态相关联 登录.md(关联笔记尚未公开)

5. 参考

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看