Nginx如何访问客户端证书的Subject Alternative Name(SAN)字段
嘿,我来帮你解决这个痛点!Nginx原生确实没有直接获取SAN的变量,但咱们有几种靠谱的方法可以在Nginx层面搞定,不用把证书丢给后端应用处理。
一、提取CN字段(原生方案,零额外模块)
首先是CN,你可以利用$ssl_client_s_dn变量,再配合Nginx的map模块精准提取CN部分,不用搞复杂的东西:
先在http块里定义一个map规则,专门从DN里抠出CN:
http { map $ssl_client_s_dn $ssl_client_cn { default ""; # 用正则匹配CN=后面的内容,直到遇到/或者结尾 ~*CN=(?<cn>[^/]+) $cn; } }
之后在你的/auth_test location里就能直接用$ssl_client_cn了,比如:
location /auth_test { return 200 "客户端证书CN: $ssl_client_cn"; }
这个正则会自动匹配DN里的CN=xxx部分,把xxx提取出来,完美解决CN的问题。
二、提取SAN字段(两种可行方案)
遗憾的是Nginx原生没有内置变量直接返回SAN,所以得用点额外手段,这里给你两种方案,按需选择:
方案1:用Lua模块(推荐,性能更优)
如果你的Nginx是OpenResty,或者已经编译安装了ngx_http_lua_module,那用Lua来解析证书是最好的选择,性能比调用外部命令好很多。
直接在location里写Lua代码,利用$ssl_client_raw_cert拿到原始证书,再调用openssl命令解析SAN:
location /auth_test { content_by_lua_block { -- 先拿到原始证书内容 local cert = ngx.var.ssl_client_raw_cert if not cert then ngx.say("没有提供有效的客户端证书") return end -- 解析SAN字段 local san_handle = io.popen("echo '" .. cert .. "' | openssl x509 -noout -text | grep -A 1 'Subject Alternative Name' | grep -v 'Subject Alternative Name'") local san = san_handle:read("*a") san_handle:close() -- 直接用前面定义好的$ssl_client_cn变量即可 local cn = ngx.var.ssl_client_cn ngx.say("客户端证书CN: ", cn) ngx.say("客户端证书SAN: ", san) } }
要是你用的是纯Nginx,记得先编译安装lua-nginx-module,同时确保服务器上有openssl命令。
方案2:调用外部Shell脚本(应急方案,性能一般)
如果不想折腾Lua模块,也可以写个简单的Shell脚本,让Nginx调用它来解析证书,但这种方式每次请求都会启动子进程,高并发场景不太推荐,适合小流量场景应急:
第一步,写个脚本/usr/local/bin/extract_cert_info.sh:
#!/bin/bash # 读取输入的证书内容 CERT=$(cat) # 提取CN CN=$(echo "$CERT" | openssl x509 -noout -subject | grep -oP 'CN=\K[^,]+') # 提取SAN SAN=$(echo "$CERT" | openssl x509 -noout -text | grep -A 1 'Subject Alternative Name' | grep -v 'Subject Alternative Name') # 输出结果 echo "客户端证书CN: $CN" echo "客户端证书SAN: $SAN"
给脚本加执行权限:chmod +x /usr/local/bin/extract_cert_info.sh
然后在Nginx配置里这样写:
location /auth_test { if ($ssl_client_raw_cert) { # 先读取请求体里的证书内容 echo_read_request_body; # 调用脚本解析 exec /usr/local/bin/extract_cert_info.sh; } return 400 "未提供有效客户端证书"; }
这个方法简单,但性能确实不如Lua方案,谨慎使用。
三、一些注意事项
- 一定要确保
ssl_verify_client设置为on或者optional,不然Nginx不会验证客户端证书,也不会生成那些证书相关的变量。 $ssl_client_raw_cert是PEM格式的原始证书,处理的时候别搞乱格式,不然openssl解析会失败。- 用Lua方案的话,建议加个缓存(比如用ngx.shared.dict),把解析过的证书信息存起来,避免每次请求都调用openssl,进一步提升性能。
内容的提问来源于stack exchange,提问作者joeweoj

