Spring Boot「27」扩展:HTTP 协议中的认证机制

211 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情

在之前的几篇文章中,我介绍了 Apache Shiro 进行安全验证的相关内容。 今天分享的是与认证相关的扩展内容,在 HTTP 协议中是如何定义或者说约定认证过程的。

认证(Authentication)指判断用户身份的过程。 通俗点讲,是让系统的使用用户自报家门,系统来核对用户身份的过程。 在 HTTP 协议上,表现为用户通过浏览器访问服务器,服务器需要核对用户生成的身份与存储在服务器端的身份信息是否匹配。 核对的信息一般包括:

  • 密码
  • 动态令牌
  • 数字证书
  • 生物认证
  • 身份卡片

01-HTTP/1.1 中的认证方式

HTTP 协议中使用的认证方式分为四种:

  • BASIC 认证
  • DIGEST 认证
  • SSL 客户端认证(借助 HTTPS 客户端证书完成认证)
  • FormBase 认证

01.1-BASIC 认证

客户端(一般指浏览器)发送请求:

GET /private HTTP/1.1
Host: example.samson.self

服务端返回响应(状态码 401),告知客户端需要认证:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Please input your ID and password!"

注:WWW-Authenticate 的格式如下,

WWW-Authenticate: <type> realm=<realm>
  • type 包括:Basic、Digest 等
  • realm 用于描述保护区或指示保护范围,用户可以据此知道访问的区域是什么。

收到响应后,浏览器会弹框提醒用户输入用户名、密码信息。

img_http_browser-login.png

用户输入密码后,浏览器提交请求头中在 Authorization 声明使用的认证方式为 Basic,并用 Base64 处理 'user:password' 串:

GET / HTTP/1.1
Host: example.samson.self
Authorization: Basic YWRtaW46YWRtaW4=

服务端响应访问成功:

HTTP/1.1 200 OK
Server: nginx/1.21.5
ETag: "5d52b25f-264"

Basic 方式缺点很明显,用户名、密码字符串仅作了 Base64 处理,很容易能够拿到明文,例如:

echo "YWRtaW46YWRtaW4=" | base64 -d
# 得到输入的用户名密码
# admin:admin

01.2-Digest 认证

digest 认证是为了解决 Basic 认证中的不足。 未认证时,服务端返回给客户端的响应头中 WWW-Authenticate 与 basic 不同。

HTTP/1.1 401 Unauthorized
Server: nginx/1.22.1
WWW-Authenticate: Digest algorithm="MD5", qop="auth", realm="realm", nonce="7daa00ea63eb28fb"

注:realm 与 basic 认证中的 realm 一样,是服务端发送的提示信息。 nonce 是临时随机质询码,是每次请求随 401 返回的随机字符串。

接受到服务端需要认证的消息后,客户端输入用户、密码:

GET /digest HTTP/1.1
Host: example.samson.self
Authorization: Digest username="lihua", realm="realm", nonce="7daa00ea63eb28fb", uri="/digest", cnonce="MTc5MWI1NjQ3ZDdiODAwNDA1NDZmNmI5YTU3ZmJlNmI=", nc=00000001, qop=auth, response="6e6e14f495abcd80c971eb99940fdfbb", algorithm="MD5"

注:请求头中的 Authorization 必须包含如下字段,

  • username,用户名
  • realm \ nonce,与随 401 消息返回的域值一样
  • uri,要访问的路径
  • response,也叫 response-digest,内容是经过 MD5 算法运算过的密码字符串

验证成功后,返回给客户端的消息头,Authentication-Info 存储认证信息:

HTTP/1.1 200 OK
Server: nginx/1.22.1
Location: http://example.samson.self/digest/
Authentication-Info: qop="auth", rspauth="8f4867c311b84f540f243b878b666f71", cnonce="MTc5MWI1NjQ3ZDdiODAwNDA1NDZmNmI5YTU3ZmJlNmI=", nc=00000001

不足之处是,digest 虽然能够保护用户密码不备窃取,但是无法防止用户伪装。

01.3-基于表单的认证

基于表单的认证一般由服务端的 Web 应用完成。 Web 应用提供登录界面,并将用户提交的身份信息与存储在系统中的信息进行比较,完成身份验证。 Apache Shiro、Spring Security 等都是针对这种情形而设计的安全认证框架。

由于 HTTP 协议是无状态的,认证通过的用户的状态信息无法在协议层面上保存下来。 所以,一般都是使用 Cookie 来管理 Session,弥补 HTTP 协议中不能管理状态的不足。

首先,HTTP Cookie 机制是针对 HTTP 协议无状态特点而设计的一种补充、改良机制。 它主要用于以下几个目的:

  • 会话管理,例如登录状态、购物车内容、游戏得分等内容
  • 个性化,例如用户偏好设置、主题设置等
  • 追踪,记录和分析用户行为

Cookie 一般存储在浏览器端,服务器端可以通过在响应头中增加 Set-Cookie 来通知浏览器创建 Cookie 并保存值。 例如:

HTTP/1.1 200 OK
Set-Cookie: foo=bar
Set-Cookie: tor=bar

浏览器在请求消息中,通过请求头中的 Cookie 字段,将 Cookie 信息发送给服务端。 例如:

GET /private HTTP/1.1
Host: example.samson.self
Cookie: foo=bar; tor=bar

Cookie 的生命周期:

  • 会话 Cookie,在浏览器关闭时被删除。它没有指定 Expires 或 Max-Age 属性。
  • 永久性 Cookie,不会在浏览器关闭时被删除,而在指定时间后过期、被删除。

如果说 Cookie 是为了保存状态,客户端在 HTTP 协议之上做的优化和改良。 那么,Session 就是服务端在 HTTP 协议上关于状态保存做的优化和改良。 Cookie、Session 往往搭配在一起使用。

基于 Cookie 管理 Session 的一般流程为:

  1. 用户提交身份(用户名、密码、双因素认证等)信息,Web 应用验证通过后,创建 Session 保留在服务端,并将与 Session 一一对应的 Session ID 发送到客户端。
  2. 服务端发送包含 Set-Cookie: JSESSIONID=xxxx 头部的消息给客户端,存储在 Cookie 中。
  3. 客户端后续发送给服务端的请求中,携带 Cookie: JSESSIONID=xxxx 头部信息的请求,服务端验证后获取到与该 Session ID 关联的 Session 对象。

02-使用 Nginx 验证 Basic 和 Digest 认证方式

02.1-基于 Basic 的认证

在 Nginx 中,可以通过 auth_basic 来开启认证功能。 例如,在 nginx.conf 中添加如下内容:

location /basic {
	auth_basic "realm";
    auth_basic_user_file <your_conf_file_path>;  // 保存用户名、密码的文件
}

注:<your_conf_file_path> 表示保存用户名、密码的文件。 其内容类似于:

# user:password
admin:$apr1$TfcEHUcD$yjfY1utC5y1ZI8atJrVew.
lihua:$apr1$.dQNLFIX$0vo.HfSUoQsDzfpC.Y3Jh0
hanmeimei:$apr1$2Fvl.nxS$kjiKl.L3GaYt2gjZLsYPI/

密码可以通过 openssl 生成,例如:

openssl passwd -apr1 admin # 生成加密内容

02.2-基于 Digest 的认证

Nginx 中代码中默认是不包含 Digest 认证功能的,可以通过引入 Module 的方式,将 Digest 验证添加到 Nginx 中并编译可执行程序。 Digest 模块的源码可以从 github 中下载到。 下载后,可以通过如下方式重新编译 Ngnix

cd ${your_nginx_source_dir}

./configure --add-module=${your_digest_source_dir}  [other configure options]

make && sudo make install

然后,在 nginx.conf 中增加如下配置:

location /digest {
	auth_digest "realm";
    auth_digest_user_file <your_conf_file_path>;
}

注:<your_conf_file_path> 是保存用户、realm、密码的文件,它其中的内容类似于:

# user:realm:password
lihua:realm:0d0707f271ef012786400ed926232c01

这个文件可以通过 htdigest 工具生成:

htdigest -c passwd.file realm username
# 按照提示输入密码
> Adding password for username in realm realm.
> New password:
> Re-type new password:

03-总结

今天,我与大家一起针对 HTTP 协议上的认证进行了扩展学习,主要包括 HTTP 协议上的三种认证方式。 特别是前两种,它是 HTTP 协议本身提供的两种认证机制,不过由于安全性不是特别高,只适用于一些比较简单的场景。 第三种,基于表单的验证实际上是将认证留给了业务系统去实现。针对这种,行业内衍生出了一些安全认证框架,例如 Apache Shiro,来帮助开发者处理相关的问题。 为了保存用户状态,在 HTTP 协议之上扩展了 Cookie + Session 机制。 不过这种机制也并非完美,同样存在不足之处,例如服务端存储 Session 需要耗费资源,在海量用户场景下压力比较大。而且,在分布式场景中,跨应用之间的会话共享也是一个需要解决的问题。 希望今天的内容能对你有所帮助。