1. HTTP 协议简介
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议之一,用于客户端和服务器之间的通信。
1.1. HTTP 基础概念
- 无状态协议:默认不保留客户端状态,每次请求独立(但可通过Cookie/Session模拟状态)。
- 请求-响应模型:客户端发送请求,服务器返回响应。
- 基于TCP/IP:默认端口80(HTTP)或443(HTTPS)。
- 明文传输:HTTP本身不加密,安全性依赖HTTPS(HTTP + TLS/SSL)。
1.2. HTTP 消息结构
请求报文
GET /index.html HTTP/1.1 # 请求行(方法 + URI + 协议版本)
Host: www.example.com # 必需头部(HTTP/1.1)
User-Agent: Mozilla/5.0 # 客户端信息
Accept: text/html # 可接受的响应类型
Connection: keep-alive # 长连接选项
[空行] # 分隔头部与主体
[请求体] # GET无请求体,POST/PUT有
Bash响应报文
HTTP/1.1 200 OK # 状态行(协议版本 + 状态码 + 描述)
Server: nginx/1.18 # 服务器信息
Content-Type: text/html # 响应体类型
Content-Length: 1024 # 响应体大小
[空行] # 分隔头部与主体
<!DOCTYPE html> # 响应体(如HTML/JSON等)
...
Bash1.3. HTTP 请求方法
方法 | 作用 | 幂等性 | 安全性 |
GET | 获取资源 | 是 | 是 |
POST | 提交数据(创建/更新) | 否 | 否 |
PUT | 替换整个资源 | 是 | 否 |
PATCH | 部分更新资源 | 否 | 否 |
DELETE | 删除资源 | 是 | 否 |
HEAD | 获取响应头(无Body) | 是 | 是 |
OPTIONS | 查询服务器支持的HTTP方法 | 是 | 是 |
幂等性:多次请求效果相同(如GET、PUT、DELETE)。
安全性:不修改服务器数据(如GET、HEAD)。
1.4. HTTP 状态码
分类 | 常见状态码 | 描述 |
1xx | 100 Continue | 客户端应继续发送请求体 |
2xx | 200 OK | 请求成功 |
201 Created | 资源已创建(POST/PUT) | |
204 No Content | 响应无Body(如DELETE成功) | |
3xx | 301 Moved Permanently | 永久重定向(更新书签) |
302 Found | 临时重定向(不更新书签) | |
304 Not Modified | 资源未修改(缓存有效) | |
4xx | 400 Bad Request | 请求语法错误 |
401 Unauthorized | 需身份认证 | |
403 Forbidden | 服务器拒绝访问 | |
404 Not Found | 资源不存在 | |
5xx | 500 Internal Server Error | 服务器内部错误 |
502 Bad Gateway | 网关/代理错误 | |
503 Service Unavailable | 服务不可用(过载/维护) |
1.5. HTTP/1.1 的高级特性
持久连接(Keep-Alive):
- HTTP 1.1 默认开启长连接,即 TCP 连接默认不会关闭,可以被多个 HTTP 请求和响应复用,减少了创建和关闭连接的消耗和延迟。
- 在 HTTP 1.0 中使用长连接需要添加请求头 Connection: Keep-Alive,而在HTTP 1.1 中所有的连接默认都是长连接,除非特殊声明不使用长连接( HTTP 请求报文首部加上 Connection: close )。
2. HTTP 协议工作流程
- 用户输入URL并触发请求
- 用户在浏览器地址栏输入URL(如
http://www.example.com
),或点击链接/提交表单。 - 浏览器解析URL,提取协议(HTTP/HTTPS)、域名(
www.example.com
)、端口(默认80/443)、路径(如/index.html
)和查询参数(如?id=123
)。
- 用户在浏览器地址栏输入URL(如
- DNS解析
- 浏览器检查本地缓存(Hosts文件、浏览器缓存、系统缓存)是否有域名对应的IP地址。
- 若未命中缓存,向DNS服务器发起递归查询,最终获取域名对应的IP(如
93.184.216.34
)。
- 建立TCP连接(三次握手)
- 步骤:
- 客户端发送
SYN=1, seq=x
报文到服务器。 - 服务器回复
SYN=1, ACK=1, seq=y, ack=x+1
。 - 客户端发送
ACK=1, seq=x+1, ack=y+1
。
- 客户端发送
- 连接建立后,进入数据传输阶段。
- HTTPS额外步骤:在TCP握手后,进行TLS握手(交换证书、协商密钥等)。
- 步骤:
- 发送HTTP请求
- 服务器处理请求
- 服务器返回HTTP响应
- 浏览器渲染页面
- 断开连接(四次挥手)
- 默认HTTP/1.1使用
Connection: keep-alive
复用连接,无需立即关闭。 - 关闭连接步骤:
- 客户端发送
FIN=1, seq=u
。 - 服务器回复
ACK=1, ack=u+1
。 - 服务器发送
FIN=1, seq=v
。 - 客户端回复
ACK=1, ack=v+1
。
- 客户端发送
- 默认HTTP/1.1使用
3. HTTP 协议会话保持
HTTP 协议本身是无状态的,但实际业务中常需要保持用户会话(如登录状态)。以下是实现 HTTP 会话保持 的常见方法及详细说明:
3.1. Cookie(客户端存储)
原理:
服务器通过响应头 Set-Cookie
向客户端(浏览器)写入键值对数据,后续请求浏览器自动通过 Cookie
请求头携带该数据。
示例流程:
- 服务器设置Cookie:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; Path=/; Expires=Wed, 19 Jun 2025 10:00:00 GMT; HttpOnly; Secure
# HttpOnly:禁止JavaScript访问(防XSS)。
# Secure:仅通过HTTPS传输。
# SameSite:限制跨站请求(防CSRF)。
Bash- 客户端发送Cookie:
GET /dashboard HTTP/1.1
Cookie: session_id=abc123
BashCookie 机制的缺陷:
- Cookie 信息通过明文的方式储存,容易泄露。
- Cookie 数据在客户端存储,可能被恶意程序(如 XSS 攻击)窃取。
为了弥补使用 Cookie 而造成的安全问题,引入了 Session 机制,下面详细介绍。
3.2. Session(服务端存储 + Cookie关联)
原理:
服务器在内存/数据库存储会话数据,通过Cookie传递唯一Session ID绑定用户。
示例流程:
- 用户登录:
- 服务端生成Session ID(如
abc123
),存储数据(如{user_id: 100, username: "Alice"}
),并通过Cookie返回ID。
- 服务端生成Session ID(如
- 后续请求:
- 浏览器携带Session ID,服务端查询对应数据。
Session 机制存在的问题:
采用 Session 机制时,Session 信息保存于服务端,而服务端一般会采用负载均衡代理的多个服务器,第一次会话可能打到了web01,然后会话信息存放到了web01上;第二次负载均衡将请求转发到了web02,便无法读取该 Session 信息了。
解决方法就是将会话信息存放到一个后端服务器共享的地方(如数据库),一般采用 redis 来储存 Session 信息效率更高。但为了防止单点故障,就需要引入 redis 集群,随着规模的发展这个集群可能会成为性能瓶颈。
所有后面又引入了下面的方案。
3.3. Token(无状态方案)
原理:
服务端不存储会话数据,通过签名Token(如JWT)验证客户端身份。
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络间安全传输声明(Claims)的轻量级令牌。它通过数字签名或加密确保数据的完整性和可信性,常用于身份验证(如替代 Session-Cookie 机制)和信息交换。
JWT 原理:
JWT 由三部分组成,用 .
分隔,格式为:Header.Payload.Signature
- Header(头部)
- 声明令牌类型(
typ: "JWT"
)和签名算法(如alg: "HS256"
或RS256
)。 - 示例:
- 声明令牌类型(
{
"alg": "HS256",
"typ": "JWT"
}
# 上面的 JSON 最终会被 Base64Url 编码成一个字符串(假设为 xxxxxx)。
Bash- Payload(状态信息)
- 示例:
{
"sub": "1234567890", # 过期时间
"name": "John Doe",
"admin": true
}
# 上面的 JSON 最终会被 Base64Url 编码成一个字符串(假设为 yyyyyy)。
Bash- Signature(签名)
- 对
Header
和Payload
的签名,防止篡改。
- 对
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
# 得到的是"header+payload+盐/密钥"三者通过头里存放得 hash 算法计算出来得 hash 值(假设为 zzzzzz)。
Bash最后得到的 JWT 为 xxxxxx.yyyyyy.zzzzzz。
JWT 工作流程(以身份验证为例):
- 用户首次登录时,服务器验证用户的凭证(如用户名和密码)。如果凭证有效,服务器则生成一个 JWT,并将其发送回客户端。
- 客户端应用程序(如使用Vue.js、React等编写的前端代码)收到 JWT 后,会将其存储在本地存储(最常用的是 localStorage,有时也会使用 sessionStorage)中。这样做是为了持久化用户会话,即使在浏览器关闭后再重新打开时,用户仍然保持登录状态。
- 当客户端应用需要向服务器发送需要认证的请求时,它会从本地存储中检索 JWT,并将其作为Authorization头的一部分附加到请求中。通常,它是以 Bearer 标记形式发送的,如 Authorization: Bearer 。
- 服务器在接收到请求时,会解析 Authorization 头,验证 JWT 的有效性(如检 查签名、是否过期等)。如果验证通过,则允许请求继续进行。如果失败,可能会返回错误响应,如 401 Unauthorized。
4. HTTP 协议加密
HTTPS(HTTP + SSL/TLS):HTTPS在HTTP基础上加入SSL/TLS加密层,默认端口443。
4.1. SSL 详解
下面先了解一些相关知识点。
4.1.1. 对称加密与非对称加密
对称加密 (Symmetric Encryption):
- 单一密钥:加密和解密使用相同的密钥。
- 高效快速:计算复杂度低,适合大数据量加密。
- 密钥分发问题:如何安全地共享密钥是主要挑战。
非对称加密 (Asymmetric Encryption):
- 密钥对:公钥(公开)和私钥(保密),公钥加密私钥解密。
- 计算复杂:比对称加密慢1000倍以上,不适合大数据量加密。
4.1.2. SSL 加密通信流程
SSL 加密则是使用了对称加密与非对称加密的结合,在握手阶段采用非对称加密,通信阶段采用对称加密,兼顾了安全与效率。下面详细介绍 SSL 加密通信流程:
单向认证:
- 握手阶段
- 客户端发起握手,发送:
- 支持的 SSL 版本
- 支持的加密套件列表,如:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
等。
- 服务器响应:
- 选择的 SSL 版本
- 选择的加密套件(此时的加密方案是明文传输,还是有一定的风险,对与安全要求高的场景可以选择双向认证)
- 服务器数字证书(包含公钥)
- 证书验证
- 客户端验证:
- 证书是否由受信任CA签发
- 证书是否在有效期内
- 证书中的域名是否匹配
- 证书是否被吊销(CRL/OCSP检查)
- 客户端验证:
- 密钥交换
- 验证通过后,客户端使用服务端选择的加密方式生成密钥(用于通信时对称加密)。
- 将生成的密钥用服务端公钥加密后发送到服务端。
- 服务端收到客户端返回的加密信息后,使用自己的私钥进行解密,获得对称加密密钥。
- 加密通信阶段
- 在接下来的会话中,服务端和客户端将会使用对称加密, 保证通信过程中信息的安全。
- 客户端发起握手,发送:
双向认证:
- 握手阶段
- 客户端发起握手,发送:
- 支持的 SSL 版本
- 支持的加密套件列表,如:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
等。
- 服务器响应:
- 请求客户端证书和公钥。
- 证书验证
- 客户端验证:
- 证书是否由受信任CA签发
- 证书是否在有效期内
- 证书中的域名是否匹配
- 证书是否被吊销(CRL/OCSP检查)
- 服务端验证:
- 当服务端向客户端请求了证书时客户端将证书和公钥发送到服务端。
- 服务端对证书进行验证。
- 客户端验证:
- 密钥交换
- 服务端验证通过后,服务端将选择的 SSL 版本和加密套件使用客户端公钥进行加密并发送给客户端。(此时加密方案不是采用明文的方式发送的,对比单向认证更安全)。
- 客户端使用服务端选择的加密方式生成密钥(用于通信时对称加密)。
- 将生成的密钥用服务端公钥加密后发送到服务端。
- 服务端收到客户端返回的加密信息后,使用自己的私钥进行解密,获得对称加密密钥。
- 客户端发起握手,发送:
- 加密通信阶段
- 在接下来的会话中,服务端和客户端将会使用对称加密, 保证通信过程中信息的安全。
4.2. TLS
TLS(Transport Layer Security)是更为安全的升级版 SSL。由于
SSL 这一术语更为常用,所以我们通常仍将 TLS 证书称作 SSL 证书。
总结概括:
- SSL 是一种早期的加密协议,1999年前后随着互联网的快速发展互联网工程任务组(IETF)对 SSL 进行了标准化,并改名为 TLS,尽管协议名称发生了 变化,但核心概念保持不变。
- TLS 基于 SSL3.0,进行了改进和升级解决了 SSL 的一些安全漏洞,相比 SSL 更加安全高效。换句话说,TLS 是 SSL 的更安全高效的版本。SSL 已基本被 TLS 取代,当前最新的协议版本是 TLS1.3。统计显示:截至2023年,全球95%的HTTPS流量已使用TLS 1.2或1.3,仅0.3%仍使用SSL协议。
5. 自签 CA 证书步骤
准备工作:
确保已安装OpenSSL(Linux/macOS通常内置,Windows需下载)。
生成自签CA根证书:
(1) 生成CA私钥
openssl genrsa -out ca.key 4096 # 推荐RSA 4096位,或ECDSA:openssl ecparam -genkey -name secp384r1 -out ca.key
Bash(2) 创建CA证书(自签)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=My Root CA/O=My Org/C=US"
# 参数说明
# -x509:生成自签名证书。
# -days 3650:有效期10年(按需调整)。
# -subj:证书主题(可省略,交互式填写)。
Bash用CA签发服务器证书:
(1) 生成服务器私钥
openssl genrsa -out server.key 2048
Bash(2) 创建证书签名请求(CSR)
openssl req -new -key server.key -out server.csr -subj "/CN=example.com/O=My Server"
# 交互式填写:若省略-subj,会提示输入国家、域名等信息。
Bash(3) 用CA签发服务器证书
创建扩展配置文件server.ext
:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:example.com,DNS:*.example.com # 多域名/通配符支持
Bash签发证书:
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256 -extfile server.ext
Bash部署与信任:
(1) 服务器配置
将server.crt
和server.key
部署到Web服务器(如Nginx):
ssl_certificate /path/to/server.crt;
ssl_certificate_key /path/to/server.key;
Bash(2) 客户端信任CA
Windows:双击ca.crt
→ “安装证书” → 选择“受信任的根证书颁发机构”。
Linux/macOS:
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt
# Linux (Debian/Ubuntu)
sudo cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
Bash