如何设计一个登录系统

很多 Web 应用都会有用户登录系统,基本流程如下:

  • 用户在登录界面输入“用户名”和“密码”,点击“登录”发送登陆请求
  • 服务端接收到登录请求并验证用户
  • 服务端返回用户凭证
  • 客户端用返回的用户凭证调用服务端相应接口

登录安全标准

上面的每一步都可能存在安全隐患,如果设计不当,很容易将用户的密码泄露出去。 要想一个登录系统安全,至少要保证以下几个方面:

通信安全

由于 Web 应用基于 HTTP 协议,而其明文传输的特性,很容易使客户端和服务端通信时被窃听,从而导致用户信息泄露, 所以客户端和服务端通信时需要加密,一般才用 HTTPS,服务端应搭建 HTTPS 服务器。

原始密码安全

由于用户的密码信息保存在服务器端,所以服务端的系统开发人员和运维人员是可以得到这些信息的, 而用户原始密码的泄露大多是因为服务端明文保存密码的原因造成的, 所以服务端需要对用户的密码加密保存。

只有服务端保存用户密码时加密就可以了吗? 也不行,客户端也应该将密码进行加密, 因为,服务端接收到登录请求时,开发人员是可以获取用户的密码信息的。

不可逆哈希加密

对密码加密应采用不可逆哈希加密,在哈希算法中,首选是 SHA2 系列,可以对密码进行多次哈希加密, 客户端可以采用如下加密方法:

sha256(
  sha265(sha265(password)) + sha265(username)
)

这样,不仅可以增加密文反推原文的难度,还加入用户名信息,即使密码相同,不同用户的密文也完全不一样。

加密混入随机数据(盐)

由于对同一字符串进行哈希的结果是恒定的,所以知道了算法和密文,理论上是可以反推出密码的,反推的难度取决于用户原始密码的复杂度。 那如何才能够让反推的难度指数级增大呢? 答案是在原始密码密文的基础之上,再加入一个随机字符串,从而达到让用户的密码更复杂的效果。 这个随机字符串,便是盐。

服务端保存密码时可以采用如下加密:

sha256(
  sha256(username + sha256(password + salt1)) + salt2 + sha256(username + salt3)
)

注意,盐的保存非常关键,务必将它与用户信息分开存放。

服务端经常更新盐和密文

前面用户的密码已经在客户端和服务端都加密了,基本已经很安全了, 但是我们还可以更安全,那就是经常变更盐,让用户信息表中的密文字段值也经常变化。

由于服务端是不存储客户端哈希密文的,所以只能在用户登录的时候才能更改盐和密文。

登录身份验证

目前,实现身份验证的方法,主要有两个大类:

Cookies

传统的浏览器网页,都是使用 Cookies 来验证身份, 实际上,浏览器端的应用层里,基本不用去管身份验证的事情, Cookies 的设置,由服务器端完成,在提交请求的时候,由浏览器自动附加对应的 Cookies 信息, 所以在 JavaScript 代码中,不需要为此编写专门的代码。 但每次请求的时候,都会带上全部的 Cookies 数据,

随着 CDN 的应用,移动端的逐渐兴起,Cookies 越来越不能满足复杂的、多域名下的身份验证需求。

密钥

当浏览器在请求服务器的时候,将密钥以特定的方式附加在请求中, 比如放在请求的头部( headers )。 为此,需要编写专门的客户端代码来管理密钥。

浏览器端密钥的保存也有以下几种方法:

  • Cookies:不推荐使用,有最大 4k 的限制
  • sessionStorage:sessionStorage 在 tab 页间是不能共享的
  • localStorage: 推荐方式,除非清理浏览器数据,否则 localStorage 存储的数据会一直存在