SSL证书检测API:参数详解与工程化落地实践
适用场景与接口定位
部署HTTPS服务的团队常需要批量监控证书有效期、检查通配符覆盖域、验证证书链是否完整。手动逐域查看浏览器地址栏显然低效,而这款SSL证书检测API恰好填补了自动化监控的空缺。它通过简单GET请求远程探测目标域名的证书元数据,输出颁发者、有效期、SHA-1指纹、远端IP等关键字段,并且内置了域名智能剥离与严格校验逻辑,减少上游无效请求。
典型应用包括:
- 证书到期预警——定时调用本API,解析
expire_days字段,当低于阈值时触发告警。 - CDN或反向代理证书一致性检查——同一域名不同解析IP的证书指纹对比。
- 安全审计——批量检测子公司或合作伙伴域名的证书状态与颁发机构。
- 边缘节点调试——通过
remote_address确认实际连接IP,辅助排查SNI问题。
接口能力边界
| 维度 | 说明 |
|---|---|
| 请求方法 | GET |
| 接口端点 | https://v1.apizero.cn/api/ssl |
| 鉴权方式 | 可选Bearer Token(Header:Authorization);匿名调用每日30次 |
| QPS限制 | 5次/秒 |
| 缓存策略 | 成功响应缓存6小时;无SSL / 连接失败响应缓存30分钟 |
| 速率限制 | 超出QPS返回429,建议配合指数退避重试 |
接口要求域名必须通过RFC 1123校验,总长度≤253字符,标签长度介于1~63。若传入非法格式(如带空格、双点号),会直接返回参数错误而不是向上游转发。
请求参数详解
Query参数
| 参数名 | 必填 | 类型 | 说明 | 示例值 |
|---|---|---|---|---|
domain | 是 | string | 待检测的域名。支持智能剥离:自动去除http://、https://、路径、端口、www.前缀。 | apizero.cn或https://www.apizero.cn/path |
智能剥离细节:
- 输入
https://blog.apizero.cn:8080/api?q=1会剥离为blog.apizero.cn。 www.前缀不会被剥离(仅去掉协议、端口、路径)。注意:通配符域*.apizero.cn不含www.,剥离后为*.apizero.cn(合法)。
Header参数
| 参数名 | 必填 | 位置 | 说明 | 示例值 |
|---|---|---|---|---|
Authorization | 否(匿名可用) | Header | Bearer Token鉴权。格式:Bearer sk_live_xxx。匿名调用每日30次。 | Bearer sk_live_xxxxxxxxxxxxxx |
注意:素材中curl示例使用的Header是
X-API-Key,但事实卡指定为Authorization。实际以最新文档为准,这里我们遵循文档中的Authorization字段,并在示例中按文档写法展示。
响应示例(完整解析)
{ "code": 0, "data": { "common_name": "*.apizero.cn", "domain": "apizero.cn", "domains": ["*.apizero.cn", "apizero.cn"], "expire_date": "2026-11-07 17:04:59", "expire_days": 184, "fingerprint": "f4633adfd1cb59185ba094dc3edeef5a8ea26889", "is_expired": false, "is_ssl": true, "issuer": "Certum DV TLS G2 R39 CA", "issuing_agency": "Asseco Data Systems S.A.", "life_span_days": 198, "remote_address": "119.36.225.184:443", "signature_algorithm": "RSA-SHA256", "start_date": "2026-04-22 17:05:00" }, "msg": "成功", "request_id": "abc123def456" }curl 接入示例(可复制)
匿名调用(每日30次)
curl -sS -X GET "https://v1.apizero.cn/api/ssl?domain=example.com"带鉴权调用(推荐用于生产)
curl -sS \ -X GET \ -H "Authorization: Bearer sk_live_你的API密钥" \ "https://v1.apizero.cn/api/ssl?domain=apizero.cn"将sk_live_你的API密钥替换为实际密钥。
编程语言接入:Rust 示例
以下使用reqwest库演示如何发送请求并解析结果。
use serde::Deserialize; use std::collections::HashMap; #[derive(Deserialize, Debug)] struct SslResponseData { common_name: Option<String>, domain: Option<String>, domains: Option<Vec<String>>, expire_date: Option<String>, expire_days: Option<i64>, fingerprint: Option<String>, is_expired: Option<bool>, is_ssl: bool, issuer: Option<String>, issuing_agency: Option<String>, life_span_days: Option<i64>, remote_address: Option<String>, signature_algorithm: Option<String>, start_date: Option<String>, } #[derive(Deserialize, Debug)] struct SslResponse { code: i32, data: Option<SslResponseData>, msg: String, request_id: String, } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let domain = "apizero.cn"; let api_key = std::env::var("APIZERO_API_KEY").ok(); let client = reqwest::Client::new(); let mut req = client .get("https://v1.apizero.cn/api/ssl") .query(&[("domain", domain)]); if let Some(key) = api_key { req = req.header("Authorization", format!("Bearer {}", key)); } let resp = req.send().await?; let body = resp.text().await?; // 注意:实际返回外层有一个数组,但文档示例直接是对象。需根据真实响应调整解析 // 这里假设直接返回对象(文档示例如此) let ssl_resp: SslResponse = serde_json::from_str(&body)?; if ssl_resp.code == 0 { if let Some(data) = ssl_resp.data { println!("证书状态: {}", if data.is_ssl { "有SSL" } else { "无SSL" }); if let Some(days) = data.expire_days { println!("到期天数: {}", days); } } } else { eprintln!("请求错误: {} - {}", ssl_resp.code, ssl_resp.msg); } Ok(()) }注意:实际响应外层可能是一个数组(如素材中
[ {...} ])。为了简洁,上述代码假设顶层是对象。生产代码应处理数组形式,例如:let ssl_resp: Vec<SslResponse> = ...; let item = ssl_resp.into_iter().next().unwrap();。具体以官方文档为准。
返回值字段解读
| 字段 | 类型 | 说明 |
|---|---|---|
code | int | 业务状态码,0表示成功 |
msg | string | 成功或错误描述 |
request_id | string | 本次请求的唯一标识,用于问题排查 |
data | object / null | 当is_ssl=false且无上游错误时,data为null(其他字段也全为null) |
data.is_ssl | bool | true表示域名配置了SSL并成功获取证书信息;false表示无证书或连接失败 |
data.common_name | string / null | 证书通用名称(CN),通常包含通配符 |
data.domains | string[] / null | 证书覆盖的所有域名(SAN扩展) |
data.expire_date | string / null | 证书到期UTC时间,格式yyyy-MM-dd HH:mm:ss |
data.expire_days | int / null | 距到期天数(以当前时间算,非证书签发时间) |
data.fingerprint | string / null | 证书SHA-1指纹(40位十六进制) |
data.is_expired | bool / null | 是否已过期(注意字段名is_expired,不是is_expire) |
data.issuer | string / null | 证书颁发者名称 |
data.issuing_agency | string / null | 颁发机构(CA公司名) |
data.life_span_days | int / null | 从start_date到expire_date的总天数 |
data.start_date | string / null | 证书生效UTC时间 |
data.signature_algorithm | string / null | 签名算法,如RSA-SHA256 |
data.remote_address | string / null | 实际连接的远端IP:端口 |
对于不含SSL的域名或连接超时,所有data内部字段均为null(除了is_ssl=false)。上游服务异常时返回HTTP 502,且code可能非0。
常见错误与异常处理
| HTTP状态码 | 业务code | 可能原因 | 处理建议 |
|---|---|---|---|
| 200 | 0 | 成功 | 正常解析 |
| 200 | 1001 | 域名格式不合法 | 检查传入的domain是否符合RFC 1123,无空格和非法字符 |
| 200 | 1002 | 上游连接失败或无证书 | 检查域名是否可达,或确认该域名确实没有启用HTTPS |
| 400 | — | 缺少必填参数domain | 确认Query中有domain字段 |
| 401 | — | API Key无效或已过期 | 检查AuthorizationHeader格式,确保Bearer后有一个空格 |
| 429 | — | QPS超限 | 加入重试逻辑,间隔至少200ms,使用指数退避(1s、2s、4s) |
| 502 | — | 上游服务异常 | 记录request_id并稍后重试,若持续失败联系服务方 |
三态响应全览:
is_ssl=true+ 完整data:正常。is_ssl=false+ data为null(所有字段null):域名无SSL或无法连接(超时、拒绝连接)。- 502 / 上游内部错误:不返回200,需按状态码处理。
工程化注意事项
- 域名剥离测试:虽然API自带剥离,但在客户端仍建议先对输入做标准化(如只传裸域名),避免传入
https://导致预期外结果。特别地,www.不会被剥离,如果关心www子域证书,应显式传入www.example.com。 - 缓存考虑:成功结果缓存6小时,意味着同一域名6秒内重复调用不会产生新请求。设计监控调度时,建议每6小时轮询一次即可,不必低于5分钟。对于无SSL域名缓存30分钟,避免短时间内反复扫无效域名。
- 并发限制:QPS为5,即每秒最多5个请求。批量检测时请加入节流(如
tokio::time::sleep(Duration::from_millis(200))循环)。 - 请求ID记录:在日志中记录
request_id,方便向API提供方反馈异常。 - 指纹校验:如果用于证书一致性检测,可与本地已知指纹对比;注意SHA-1指纹可能在未来浏览器不再信任,但API目前仍返回SHA-1。
- 无效null字段:当
is_ssl=false时,所有字段均为null。代码中务必用Option处理,避免直接解引用导致panic。
参考文档
- SSL证书检测API官方文档
- 原始文档Markdown