基于ssl客户端证书指纹实现严格身份认证,关键在于比对证书sha-256指纹而非仅依赖ca签发验证,通过openresty的access_by_lua_block提取$ssl_client_raw_cert、转der后计算哈希,并与预置白名单比对,不匹配即ngx.exit(403),从而将认证粒度精确到单张证书,防止合法证书被复制冒用。

要基于 SSL 客户端证书指纹实现严格身份认证,关键不是只验证证书是否由可信 CA 签发,而是进一步锁定具体证书——即比对证书的唯一指纹(如 SHA-256 摘要),做到“只认这张证,不认其他任何同 CA 签发的证”。这在高安全场景(如核心运维入口、特权设备接入)中非常必要,能有效防止合法证书被复制或误用。
为什么单靠 CA 验证不够?
默认的 ssl_verify_client on + ssl_client_certificate 只校验证书签名链和有效期,只要客户端持有该 CA 下任意一张有效证书,就能通过。攻击者若获取到一张已授权的客户端证书(比如从某台离职员工电脑未清理的浏览器中导出),就可冒充身份访问系统。而指纹白名单把控制粒度精确到单张证书,相当于给每张会员卡配了唯一防伪码。
配置方式:用 Lua 脚本做指纹比对
Nginx 原生不支持证书指纹匹配,需借助 nginx-module-lua(OpenResty 或编译时启用 lua 支持)。核心逻辑是在 TLS 握手完成后、请求转发前,提取客户端原始证书,计算其 SHA-256 指纹,并与预设白名单比对。
- 确保 Nginx 已加载 lua 模块,且启用了
ssl_client_certificate和ssl_verify_client on - 在 server 或 location 块中添加
access_by_lua_block - 脚本内调用
ngx.ssl和openssl.x509模块解析$ssl_client_raw_cert - 使用
openssl.digest("sha256", cert_der)计算二进制 DER 格式证书的摘要 - 将结果转为小写十六进制字符串,与白名单(如
{"a1b2c3...", "d4e5f6..."})逐一对比 - 不匹配则执行
ngx.exit(403)
如何生成和管理指纹白名单
指纹必须基于证书的 DER 编码计算,而非 PEM 文本。可用 OpenSSL 快速提取:
- 把客户端证书(如
alice.crt)转为 DER:openssl x509 -in alice.crt -outform der -out alice.der - 计算 SHA-256 指纹:
sha256sum alice.der | cut -d' ' -f1(Linux)或shasum -a 256 alice.der | cut -d' ' -f1(macOS) - 得到类似
8f3a7e2d1b9c4a6f8e0d2c1b5a9f7e3d4c6b8a0f1e2d3c4b5a6f7e8d9c0b1a2f的 64 位字符串 - 所有允许的指纹汇总为 Lua 表,放在配置中或外部文件里(注意文件权限和热加载机制)
注意事项与常见问题
这个方案看似简单,实操中几个细节容易出错:
-
必须先完成基础 mTLS 配置:
ssl_verify_client on和ssl_client_certificate是前提,否则$ssl_client_raw_cert为空 - 不能用 PEM 内容直接算哈希:PEM 包含头尾标记和 base64 换行,必须解码为 DER 后再计算
- 注意证书更新周期:客户端证书过期后,新证书指纹不同,需同步更新白名单,建议搭配自动化工具或 CMDB 管理
-
日志与调试:初期可在 Lua 中加
ngx.log(ngx.INFO, "fingerprint: ", fp)查看实际计算值,确认与预期一致











