1. 1. 传统的登录方式
  2. 2. 传统登录方式存在的问题
    1. 2.1. 密码登录
    2. 2.2. 验证码登录
  3. 3. 登录的本质
  4. 4. 非对称加密
  5. 5. 数字签名
  6. 6. WebAuthn(Web Authentication)
  7. 7. WebAuthn 拿来即用版API
    1. 7.1. 注册流程
    2. 7.2. 登录流程
  8. 8. Demo

在聊这个功能开发之前,我们先来看看传统的登录是怎么做的

传统的登录方式

【密码登录】: 客户端输入【用户名】【密码】==》服务端根据【用户名】找到用户,对密码进行MD5加密,与数据库存储的用户密码进行比对,比对成功,则登录成功,否则则失败。

【验证码登录】: 客户端输入【手机号】或【邮箱】,然后点一个按钮让服务端给手机或邮箱发送验证码,然后用户去收验证码,然后在把验证码和手机号发给服务端,服务端再根据手机号查找在有效期内的验证码,找到则登录成功,否则登录失败

【手机扫码登录】: 客户端在登录时选择二维码登录,然后用户掏出手机扫码确认。BUT:这样操作的前提是你在手机上已经进行了登录,而你在手机上的登录依旧绕不过以上两种方式

【三方授权登录】: 客户端选择微信登录,然后跳转微信用户授权,授权成功即可登录成功。BUT:这样操作的前提是你微信登录了,而你微信登录的方式依旧没有饶过密码或验证码登录

传统登录方式存在的问题

也许你会觉得我纠结这么多,有什么意义呢。那我们就来分析一下传统登录方式的问题

密码登录

为了避免麻烦,我们通常会给很多不同的应用,设置相同的密码;而用户名通常可以使用手机号或者邮箱,这就使得用户的密码一旦泄漏,其他常见的应用也可以被登录。

当然好的方式,肯定是给每一个应用都设置不同的密码,且密码尽可能的复杂,但带来的问题就是用户的记忆成本,现实场景中,几乎没有人会这么做。

验证码登录

为了账户安全,避免密码撞库,现在验证码登录已经变得非常常见,非常多的网站,在用户输入用户名后,给用户的手机/邮箱发送一个验证码,用户输入验证码后,服务端进行验证码比对,比对通过则登录成功,否则登录失败。

这样安全性固然提升了,但是成本完全转嫁给了应用的开发者,除了正常的验证码发送费用,可能会存在被恶意人员进行短信额度盗刷的风险

登录的本质

分析了这么多,我们来思考下登录的本质是什么,我们为什么需要登录。

如果没有登录,我们的功能是什么样的,如果没有登录,我们向服务端发送一条消息,将A账户的钱全部转到B账户,服务端收到这条消息,服务端敢转吗???为什么不敢转,因为我不知道这个消息是谁发的,只有当我确切的知道是A发送的消息,A指使我将它账户的钱转给B,我才敢执行这个操作,因为出了问题,是A自己的行为,不会找我服务端的麻烦。

所以我们发现了吗,我们登录的本质不在于登录方式,而在于证明你是你

所以如果放弃密码验证码,我们还有没有方式,在代码程序上证明你是你,这就是本篇文章要做的事

非对称加密

看到这里也许你会问,不是在说登录吗,为什么聊到非对称加密,不要急,要讲清楚如何证明你是你的这个问题,我们需要用到非对称加密,下面请我们记住以下几条公理与非对称加密数据传输过程示意图

  1. 一个公钥必定有唯一的一个私钥与之对应
  2. 公钥可以给予任何人,可以公开,但私钥必须自己独有,不能给予任何人
  3. 公钥可以用来加密,公钥加密的内容,只有与之对应的私钥可以解密
  4. 公钥加密的内容,不能用公钥解密,只能与之对应的私钥可以解密
img

数字签名

记住非对称加密的公理后,我们在来聊聊数字签名,此时请记住,我们的目的在于证明你是你,做这个操作的人是你,而不是为了数据安全

所以数字签名虽然利用了非对称加密的特性,但和公钥加密是完成相反的:仍然公布公钥,但是用你的私钥加密数据,然后把加密的数据公布出去,这就是数字签名

你可能问,这有什么用,公钥可以解开私钥加密,我还加密发出去,不是多此一举吗?

是的,但是数字签名的作用本来就不是保证数据的机密性,而是证明你的身份,证明这些数据确实是由你本人发出的。

你想想,你的私钥加密的数据,只有你的公钥才能解开,那么如果一份加密数据能够被你的公钥解开,不就说明这份数据是你(私钥持有者)本人发布的吗

当然,加密数据仅仅是一个签名,签名应该和数据一同发出,具体流程应该是:

1、Bob 生成公钥和私钥,然后把公钥公布出去,私钥自己保留。

2、用私钥加密数据作为签名,然后将数据附带着签名一同发布出去

3、Alice 收到数据和签名,需要检查此份数据是否是 Bob 所发出,于是用 Bob 之前发出的公钥尝试解密签名,将收到的数据和签名解密后的结果作对比,如果完全相同,说明数据没被篡改,且确实由 Bob 发出。

为什么 Alice 这么肯定呢,毕竟数据和签名是两部分,都可以被掉包呀?原因如下:

1、如果有人修改了数据,那么 Alice 解密签名之后,对比发现二者不一致,察觉出异常。

2、如果有人替换了签名,那么 Alice 用 Bob 的公钥只能解出一串乱码,显然和数据不一致。

3、也许有人企图修改数据,然后将修改之后的数据制成签名,使得 Alice 的对比无法发现不一致;但是一旦解开签名,就不可能再重新生成 Bob 的签名了,因为没有 Bob 的私钥。

WebAuthn(Web Authentication)

了解清楚以上内容后,那对于我们将要实现的功能:无需密码和验证码的登录功能,我们就知道背后的原理是什么了。

此时我们需要使用一个新的技术了 WebAuthnhttps://developer.mozilla.org/zh-CN/docs/Web/API/Web_Authentication_API

使用WebAuthn之后的注册登录流程就变为了

如果客户端想要在服务端注册一个账号,则只需要输入账号,然后在客户端本地生成一个密钥对,将账号与公钥发送服务端,服务端保存用户名于公钥即注册完成。

如果客户端想要登录一个账号,则只需要输入自己的用户名,然后在本地用私钥加密一段数据作为签名,然后再将数据与签名发送到服务端,服务端使用这个账号的公钥进行对签名解密,如果解密成功,且收到的数据与签名解密后的数据一致,则说明,数据未被修改且这个请求的发起者确实拥有这个账号的私钥,则登录成功

WebAuthn 拿来即用版API

WebAuthn 本身的API也不算复杂,但是比较原生,用起来没那么方便,所以我们就直接使用一个三方库来进行处理。

simplewebauthn :https://simplewebauthn.dev/docs/advanced/example-project

注册流程

  1. 客户端发起注册流程,将用户想要注册的账号发到服务端
  2. 服务端调用 generateRegistrationOptions 方法,生成客户端调用生成密钥对所需的数据,然后将数据返回
  3. 客户端收到数据,将数据传给 startRegistration 方法,生成公钥,将账号与 startRegistration 方法返回的数据传给服务端
  4. 服务端将收到的数据传给 verifyRegistrationResponse 方法,进行验证,验证通过则注册成功,保存相关信息

登录流程

  1. 客户端发起登录流程,将用户要登录的账号发送到服务端
  2. 服务端调用 generateAuthenticationOptions 方法,生成客户端调用登录授权方法所需要的参数,然后将数据返回
  3. 客户端收到数据,将数据传给 startAuthentication 方法,生成授权签名信息,将数据传给服务端
  4. 服务端将收到的数据传给 verifyAuthenticationResponse 方法,进行身份验证,验证通过则登录成功

Demo