借助ssh实现web服务的安全验证
背景
- 公有云服务器 http 服务 80端口,想做到安全访问
- 无须HTTPS + 客户端证书
- 方便、快捷、安全
SSH 隧道 + 本地代理
使用 SSH 隧道将 HTTP 服务“隐藏”在 SSH 之后:
# 客户端建立隧道(将本地 8080 转发到服务器的 80 端口)
ssh -L 8080:localhost:80 user@your-server -i ~/.ssh/id_ed25519# 然后访问本地端口(需先通过 SSH 认证)
curl http://localhost:8080
- 适用场景:临时授权访问,不适合公开服务。
- 在公有云的安全组中可以禁止80端口,也不影响在本地访问 http://localhost:8080
因为 SSH 隧道(端口转发)的流量是通过 SSH 协议(默认 22 端口) 传输的,与目标服务(如 HTTP 的 80 端口)无关。
其他方式
✅ 方案一:使用客户端证书(TLS Mutual Authentication)
这是最接近 SSH 公钥认证的做法。
✦ 原理(TLS 双向认证):
- 客户端持有私钥 + 客户端证书
- 服务端信任某个 CA(或特定证书)
- 在 TLS 握手阶段,客户端用私钥签名一个随机值,服务端验证客户端证书和签名
- 验证通过后,允许访问
✦ 实现步骤(以 Nginx 为例):
1. 创建自签 CA:
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt
2. 为客户端生成证书:
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256
3. 配置 Nginx 启用客户端认证:
server {listen 443 ssl;server_name example.com;ssl_certificate /path/to/server.crt;ssl_certificate_key /path/to/server.key;ssl_client_certificate /path/to/ca.crt; # 信任的CAssl_verify_client on;location / {root /var/www/html;}
}
4. 客户端访问时使用浏览器导入证书:
- Chrome / Firefox 支持在“证书管理”中导入
.crt
和.key
文件 - 或用 curl 测试:
curl -v --cert client.crt --key client.key https://example.com/
✅ 方案二:基于 JWT(非对称签名)模拟公钥认证
如果 TLS 客户端证书太重,你可以用 JWT 模拟“公钥签名验证”机制。
✦ 原理:
- 客户端持有私钥,每次请求前用私钥签名一个 payload,生成 JWT
- 服务端用已知公钥验证签名,确认身份
✦ 实现逻辑(伪代码):
客户端(Python 示例):
import jwt
from datetime import datetime, timedeltaprivate_key = open("client.key").read()payload = {"sub": "client1","exp": datetime.utcnow() + timedelta(minutes=5)
}token = jwt.encode(payload, private_key, algorithm="RS256")
# 在 header 中发送
headers = {"Authorization": f"Bearer {token}"}
服务端验证(Python Flask 示例):
from flask import request
import jwtpublic_key = open("client.pub").read()token = request.headers.get("Authorization").split()[1]try:payload = jwt.decode(token, public_key, algorithms=["RS256"])print("Client ID:", payload["sub"])
except jwt.ExpiredSignatureError:return "Token expired", 401
except jwt.InvalidTokenError:return "Invalid token", 403
这种方式适合你已有 Web 架构,且不想涉及证书分发的问题。
✅ 方案三:基于公钥的 HTTP 签名(Http Signature)
这是类似于 AWS、GitHub Webhook 的请求签名机制。
✦ 原理:
- 请求者用私钥签名 HTTP 请求(方法、时间戳、body 等)
- 服务端使用公钥验证签名是否匹配
✦ 适用场景:
- REST API 鉴权
- 高安全需求的 webhook 回调
- 可接入现有 Web 服务中间件
✦ 示例实现:
参考规范:
- IETF HTTP Signatures Draft
- 实现库如 Node 的
http-signature
, Python 的requests-http-signature
🧠 总结对比
方案 | 是否基于公钥 | 浏览器支持 | 安全性 | 部署复杂度 |
---|---|---|---|---|
TLS 客户端证书 | ✅ 是 | ✅ | ★★★★★ | 高 |
JWT + 私钥签名 | ✅ 是 | ❌(仅后端) | ★★★★ | 中 |
HTTP 请求签名(HttpSig) | ✅ 是 | ❌ | ★★★★ | 中 |
HTTP Basic / Cookie / 密码 | ❌ 否 | ✅ | ★★ | 低 |