前面已经介绍过,HTTP 协议本身是无状态的,服务器并不知道客户端发起请求的用户身份,为了搞清楚对方是谁,就需要客户端自报家门。客户端表明自己身份的方式主要有以下几种:
而客户端上报身份之后,服务端还要对其进行验证,才能证明身份的真实性,通过 HTTP 进行认证的方式主要有以下几种:
下面,我们就这种 HTTP 认证的实现做一些简单的介绍。
基本认证
HTTP/1.0 中定义了基本认证的实现,HTTP/1.1 仍然支持,Laravel 框架中提供的 HTTP 基本认证功能就是基于此标准实现的,下面我们以 Laravel 框架中的基本认证功能为例演示下基本认证的实现过程。
首先,在 routes/web.php
中定义一个基本认证路由如下:
Route::get('/auth/basic', function () {
return 'HTTP Basic Auth';
})->middleware('auth.basic');
然后我们在浏览器中访问这个路由,会返回 401 Unauthorized 响应,并且在响应头中包含 www-authenticate: Basic
,表明需要通过 Basic 认证才能访问这个路由。同时,会在页面弹出表单输出框,要求客户端输入认证凭证:
我们在表单中输入用户认证需要的凭证信息(这里的用户名是用户标识,不一定就是用户名,也可以是用户邮箱),点击确定按钮,即可发起认证请求,按照 HTTP 协议的约定,会在请求头中设置 Authorization 字段,字段值是认证方式和认证凭证:
Authorization: Basic eWFvamluYnVAMTYzLmNvbTp0ZXN0MTIzNDU2
凭证信息是用户名和密码通过冒号拼接起来,然后通过 Base64 进行编码,可以通过 Base64 解码即可还原,所以基本等同于明文传输。
接收到包含 Authorization 请求头的服务器,会对凭证信息的正确性进行验证,验证通过,则返回一条包含 Request URL 资源的响应,否则仍然返回 401 响应,要求用户继续进行认证。
摘要认证
显然,HTTP 基本认证虽然简单,但不够安全,为此,HTTP/1.1 引入了摘要(Digest)认证,摘要认证和基本认证流程一样,只是不会通过明文传递密码,它提供了防止密码被窃听的保护机制,但是不能有效防止用户伪装,而 HTTPS 则能够兼顾这两方面的安全需求,因此,摘要认证并没有得到广泛应用,随着 HTTPS 的流行而逐渐销声匿迹。
SSL客户端认证
使用用户名(或者邮箱、手机号等其它标识字段)和密码的认证方式,只要二者的内容正确,即可认为是用户本人的认证行为,但如果用户名和密码被盗,就很有可能被第三者冒充。利用 SSL 客户端认证则可以避免这种情况的发生。
SSL 客户端认证是借助 HTTPS 的客户端证书完成认证的方式,就像 HTTPS 的服务端证书可以确保服务器的合法性一样,HTTPS 的客户端证书可以确保登录设备是经过认证的合法设备。为了实现 SSL 客户端认证,需要先将客户端证书颁发给客户端,且客户端必须安装此证书才能进行认证,客户端认证虽然安全,但操作更加繁琐,一般用于对安全性要求比较高的系统认证,比如银行的网银认证系统通常就是基于 SSL 客户端认证。
SSL 客户端认证其实就是上篇分享介绍的 HTTPS 服务器端认证的反过程,客户端在认证时将客户端证书提交给服务器,服务器验证其合法性,如果合法则从证书中获取公钥,然后通过这个公钥开始与客户端的 HTTPS 通信。
多数情况下,SSL 客户端认证会结合表单认证(马上就会讲到)形成双因素认证(Two-Factor Authentication),所谓双因素认证指的是除了密码信息外,还需要认证者提供其它持有信息以增强安全性,这里,我们用 SSL 客户端证书认证登录设备,用密码信息确认是用户本人的认证行为。
另外,客户端证书和服务端证书一样,也要第三方权威认证机构进行认证,所以需要支付相应的费用,而且这个费用很高昂,这也是一般很少用 SSL 客户端证书的原因。
表单认证
表单认证并不是在 HTTP 协议中定义的,客户端会通过表单向服务器上的 Web 应用程序发送登录凭证(Credential),服务端按登录凭证的验证结果对用户进行认证。这也是我们最熟悉的认证方式:
这种认证方式结合 HTTPS 通信,无论从便利性和安全性上来说,都是比较合适的,因此是现在最主流的 HTTP 认证实现方式。
之所以说不是 HTTP 协议所定义的实现方式,是因为这种认证使用了基于 Cookie 的 Session 技术,这两者都是为了解决 HTTP 协议本身的无状态性而诞生的应用级解决方案,用户认证成功后,会保存到服务器的 Session 里面,同时在响应头中将 Session ID 设置到 Set-Cookie 字段,这样,后续客户端发起请求中就会在 Cookie 请求头中包含维护用户登录状态的 Session ID,服务器从请求报文中解析出这个 Session ID 后,在 Session 有效期内就能够识别这个客户端是已认证的客户端,从而在 HTTP 通信中实现了状态管理功能。
另外,为了增强密码的安全性,降低其被破解的几率,通常我们以加盐(Salt)并计算散列值的方式保存密码。