NOTE
2.7 如何设计用户注册登录功能
用户注册与登录的历史学习笔记,覆盖登录态、用户名密码、验证码与第三方登录的基本设计思路。
这是历史学习笔记,可能存在过时或不完整的理解。
1. 什么是登陆态
登录后,系统会返回用于标识用户身份的登录凭证,并在后续请求中据此识别用户。 不同场景可以使用不同权限范围和有效期的凭证。具体票据类型、命名、权限关系和有效期属于实现细节,这里只保留抽象概念。
2. 用户名+密码
2.1. 安全要求
首先保障数据很难被拖库。 即使数据被拖库,攻击者也无法从中破解出用户的密码 即使数据被拖库,攻击者也无法伪造登录请求通过验证。 即使数据被拖库,攻击者劫持了用户的请求数据,也无法破解出用户的密码
2.2. 注册流程
- 用户输入明文密码
password = 123456
- 客户端对密码进行普通Hash
client_hash = MD5(password) // e10adc3949ba59abbe56e057f20f883e- 如果攻击者截获了请求,那么可以知道明文密码,所以这步的作用在于避免传输明文密码
- 客户端对2的结果加固定盐进行慢Hash
client_hash = BCrypt(MD5(password) + salt) // MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2- 加salt的作用在于如果password很短,那么可以反推出password,加了salt之后再进行Hash就很难反推了
- 即使不能反推,那么可以用正推(彩虹表暴力穷举)出password,所以这里使用的
BCrypt而不是MD5的原因在于,BCrypt是个很慢的Hash函数,这样暴力穷举时就需要花费很长的时间
- 客户端传递3的结果给服务端
- 服务端生成随机盐
SecureRandom random = new SecureRandom(); byte server_salt[] = new byte[36]; random.nextBytes(server_salt); // tq2pdxrblkbgp8vt8kbdpmzdh1w8bex - 服务端对第4步客户端传过来的Hash值+第5步随机生成的盐进行Hash
server_hash = SHA256(client_hash + server_salt); // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67- 这步的作用在于防止拖库后泄露密码
- 服务端把第6步的结果+第5步随机生成的盐存储到数据库中
DB.save(server_hash, server_salt);
2.3. 登录流程
- 用户输入明文密码
password = 123456
- 客户端对密码进行普通Hash
client_hash = MD5(password) // e10adc3949ba59abbe56e057f20f883e
- 客户端对2的结果加固定盐进行慢Hash
client_hash = BCrypt(MD5(password) + salt) // MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2
- 客户端传递3的结果给服务端
- 服务端取出数据库中该用户的Hash值和盐值
- 服务端对第4步的结果+第5步的盐值进行Hash得出结果
result = SHA256(authentication_hash + server_salt); // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67 - 对比第6步计算结果和第5步取出的Hash值是否相同
3. 手机号+验证码
登录.md(关联笔记尚未公开)
4. 第三方登录
通过OAuth.md拿到第三方的OpenID,再把OpenID和自己的登陆态相关联 登录.md(关联笔记尚未公开)
讨论
使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看