在聊这个功能开发之前,我们先来看看传统的登录是怎么做的
传统的登录方式
【密码登录】: 客户端输入【用户名】【密码】==》服务端根据【用户名】找到用户,对密码进行MD5加密,与数据库存储的用户密码进行比对,比对成功,则登录成功,否则则失败。
【验证码登录】: 客户端输入【手机号】或【邮箱】,然后点一个按钮让服务端给手机或邮箱发送验证码,然后用户去收验证码,然后在把验证码和手机号发给服务端,服务端再根据手机号查找在有效期内的验证码,找到则登录成功,否则登录失败
【手机扫码登录】: 客户端在登录时选择二维码登录,然后用户掏出手机扫码确认。BUT:这样操作的前提是你在手机上已经进行了登录,而你在手机上的登录依旧绕不过以上两种方式
【三方授权登录】: 客户端选择微信登录,然后跳转微信用户授权,授权成功即可登录成功。BUT:这样操作的前提是你微信登录了,而你微信登录的方式依旧没有饶过密码或验证码登录
传统登录方式存在的问题
也许你会觉得我纠结这么多,有什么意义呢。那我们就来分析一下传统登录方式的问题
密码登录
为了避免麻烦,我们通常会给很多不同的应用,设置相同的密码;而用户名通常可以使用手机号或者邮箱,这就使得用户的密码一旦泄漏,其他常见的应用也可以被登录。
当然好的方式,肯定是给每一个应用都设置不同的密码,且密码尽可能的复杂,但带来的问题就是用户的记忆成本,现实场景中,几乎没有人会这么做。
验证码登录
为了账户安全,避免密码撞库,现在验证码登录已经变得非常常见,非常多的网站,在用户输入用户名后,给用户的手机/邮箱发送一个验证码,用户输入验证码后,服务端进行验证码比对,比对通过则登录成功,否则登录失败。
这样安全性固然提升了,但是成本完全转嫁给了应用的开发者,除了正常的验证码发送费用,可能会存在被恶意人员进行短信额度盗刷的风险
登录的本质
分析了这么多,我们来思考下登录的本质是什么,我们为什么需要登录。
如果没有登录,我们的功能是什么样的,如果没有登录,我们向服务端发送一条消息,将A账户的钱全部转到B账户,服务端收到这条消息,服务端敢转吗???为什么不敢转,因为我不知道这个消息是谁发的,只有当我确切的知道是A发送的消息,A指使我将它账户的钱转给B,我才敢执行这个操作,因为出了问题,是A自己的行为,不会找我服务端的麻烦。
所以我们发现了吗,我们登录的本质不在于登录方式,而在于证明你是你
所以如果放弃密码验证码,我们还有没有方式,在代码程序上证明你是你,这就是本篇文章要做的事
非对称加密
看到这里也许你会问,不是在说登录吗,为什么聊到非对称加密,不要急,要讲清楚如何证明你是你的这个问题,我们需要用到非对称加密,下面请我们记住以下几条公理与非对称加密数据传输过程示意图
- 一个公钥必定有唯一的一个私钥与之对应
- 公钥可以给予任何人,可以公开,但私钥必须自己独有,不能给予任何人
- 公钥可以用来加密,公钥加密的内容,只有与之对应的私钥可以解密
- 公钥加密的内容,不能用公钥解密,只能与之对应的私钥可以解密
数字签名
记住非对称加密的公理后,我们在来聊聊数字签名,此时请记住,我们的目的在于证明你是你,做这个操作的人是你,而不是为了数据安全
所以数字签名虽然利用了非对称加密的特性,但和公钥加密是完成相反的:仍然公布公钥,但是用你的私钥加密数据,然后把加密的数据公布出去,这就是数字签名。
你可能问,这有什么用,公钥可以解开私钥加密,我还加密发出去,不是多此一举吗?
是的,但是数字签名的作用本来就不是保证数据的机密性,而是证明你的身份,证明这些数据确实是由你本人发出的。
你想想,你的私钥加密的数据,只有你的公钥才能解开,那么如果一份加密数据能够被你的公钥解开,不就说明这份数据是你(私钥持有者)本人发布的吗
当然,加密数据仅仅是一个签名,签名应该和数据一同发出,具体流程应该是:
1、Bob 生成公钥和私钥,然后把公钥公布出去,私钥自己保留。
2、用私钥加密数据作为签名,然后将数据附带着签名一同发布出去。
3、Alice 收到数据和签名,需要检查此份数据是否是 Bob 所发出,于是用 Bob 之前发出的公钥尝试解密签名,将收到的数据和签名解密后的结果作对比,如果完全相同,说明数据没被篡改,且确实由 Bob 发出。
为什么 Alice 这么肯定呢,毕竟数据和签名是两部分,都可以被掉包呀?原因如下:
1、如果有人修改了数据,那么 Alice 解密签名之后,对比发现二者不一致,察觉出异常。
2、如果有人替换了签名,那么 Alice 用 Bob 的公钥只能解出一串乱码,显然和数据不一致。
3、也许有人企图修改数据,然后将修改之后的数据制成签名,使得 Alice 的对比无法发现不一致;但是一旦解开签名,就不可能再重新生成 Bob 的签名了,因为没有 Bob 的私钥。
WebAuthn(Web Authentication)
了解清楚以上内容后,那对于我们将要实现的功能:无需密码和验证码的登录功能,我们就知道背后的原理是什么了。
此时我们需要使用一个新的技术了 WebAuthn :developer.mozilla.org/zh-CN/docs/...
使用WebAuthn之后的注册登录流程就变为了
如果客户端想要在服务端注册一个账号,则只需要输入账号,然后在客户端本地生成一个密钥对,将账号与公钥发送服务端,服务端保存用户名于公钥即注册完成。
如果客户端想要登录一个账号,则只需要输入自己的用户名,然后在本地用私钥加密一段数据作为签名,然后再将数据与签名发送到服务端,服务端使用这个账号的公钥进行对签名解密,如果解密成功,且收到的数据与签名解密后的数据一致,则说明,数据未被修改且这个请求的发起者确实拥有这个账号的私钥,则登录成功
WebAuthn 拿来即用版API
WebAuthn 本身的API也不算复杂,但是比较原生,用起来没那么方便,所以我们就直接使用一个三方库来进行处理。
simplewebauthn :simplewebauthn.dev/docs/advanc...
注册流程
- 客户端发起注册流程,将用户想要注册的账号发到服务端
- 服务端调用 generateRegistrationOptions 方法,生成客户端调用生成密钥对所需的数据,然后将数据返回
- 客户端收到数据,将数据传给 startRegistration 方法,生成公钥,将账号与 startRegistration 方法返回的数据传给服务端
- 服务端将收到的数据传给 verifyRegistrationResponse 方法,进行验证,验证通过则注册成功,保存相关信息
登录流程
- 客户端发起登录流程,将用户要登录的账号发送到服务端
- 服务端调用 generateAuthenticationOptions 方法,生成客户端调用登录授权方法所需要的参数,然后将数据返回
- 客户端收到数据,将数据传给 startAuthentication 方法,生成授权签名信息,将数据传给服务端
- 服务端将收到的数据传给 verifyAuthenticationResponse 方法,进行身份验证,验证通过则登录成功