1. 为什么会出现"no subject alternative names present"错误?
当你用java程序通过https访问某个网站时,突然蹦出"java.security.cert.certificateexception: no subject alternative names present"这个错误,十有八九是因为ssl证书配置出了问题。这个错误的核心在于证书缺少主题备用名称(subject alternative names,简称sans),而现代java安全机制强制要求验证这个字段。
简单来说,sans就像是一张身份证上的多个身份信息。传统的ssl证书只认**common name(cn)**字段,就像身份证上只写了一个名字。但现实情况是,一个网站可能有多个域名(比如example.com和www.example.com),甚至需要支持ip地址直接访问。这时候就需要sans来记录所有这些合法的访问方式。
我在实际项目中遇到过这样一个案例:开发团队为内部测试环境配置了https,用openssl生成了自签名证书,只填写了cn字段。结果用java程序调用接口时,就遇到了这个经典错误。后来发现是因为jdk 1.8之后加强了对sans的校验,而他们生成的证书没有包含这个扩展字段。
2. 深入理解sans的工作原理
2.1 sans与cn字段的历史演变
早期ssl/tls证书验证主要依赖cn字段,但随着互联网发展,这种单一标识的方式暴露出了明显缺陷:
- 一个证书只能对应一个域名
- 不支持通配符以外的多域名配置
- ip地址无法被有效验证
sans扩展就是为了解决这些问题而生的。它可以包含多种类型的标识:
- dns名称:example.com、*.example.com
- ip地址:192.168.1.1
- 电子邮件地址:user@example.com
- uri:https://example.com
2.2 java的严格验证机制
java安全模型对证书验证特别严格,尤其是jdk 1.8及更高版本。当java程序建立https连接时,会执行以下验证步骤:
- 检查证书是否过期
- 验证证书链是否可信
- 核对当前访问的主机名是否匹配证书中的标识
- 先检查sans字段
- 如果没有sans,才回退到cn字段
- 如果两者都不匹配,就抛出我们看到的错误
这种机制导致很多历史遗留证书在现代java环境下无法正常工作。我见过不少团队在升级jdk版本后突然出现这个错误,就是因为老证书没有配置sans扩展。
3. 测试环境下的解决方案
对于开发和测试环境,我们可以用openssl快速生成包含sans的自签名证书。下面是我在实际工作中总结的最佳实践:
3.1 准备sans配置文件
首先创建一个san.cnf文件,内容如下:
[req] req_extensions = v3_req [v3_req] subjectaltname = @alt_names [alt_names] dns.1 = example.com dns.2 = *.example.com ip.1 = 192.168.1.100
这个配置文件定义了:
- 两个dns记录:主域名和通配符子域名
- 一个ip地址访问方式
3.2 分步生成证书
# 生成私钥 openssl genpkey -algorithm rsa -out server.key # 生成证书签名请求(csr) openssl req -new -key server.key -out server.csr \ -subj "/c=cn/st=beijing/l=beijing/o=mycompany/cn=example.com" \ -reqexts v3_req -config san.cnf # 生成自签名证书 openssl x509 -req -days 365 -in server.csr \ -signkey server.key -out server.crt \ -extfile san.cnf -extensions v3_req
关键参数说明:
-subj:设置证书主体信息,cn必须与主要域名一致-reqexts和-extensions:启用sans扩展配置-days:证书有效期,测试环境可以设长一些
3.3 快速验证证书
生成证书后,可以用这个命令检查sans是否设置成功:
openssl x509 -in server.crt -text -noout | grep -a 1 "subject alternative name"
正确输出应该显示你在配置文件中定义的所有sans条目。
4. 生产环境处理方案
生产环境的证书管理要谨慎得多,这里分享几种常见场景的解决方案:
4.1 联系证书颁发机构(ca)
正规ca颁发的证书通常都包含sans扩展。如果遇到这个问题:
- 检查现有证书是否包含正确的sans:
openssl x509 -in production.crt -text -noout | grep -a 1 "subject alternative name"
- 如果没有或不全,联系ca重新签发证书
- 提供需要添加的所有域名和ip地址
大多数商业ca都提供免费重新签发服务。去年我们为一个电商平台迁移到新域名时,就通过digicert在2小时内获得了更新后的多域名证书。
4.2 代码级解决方案(最后手段)
如果暂时无法更新证书,可以修改java代码绕过验证(仅限紧急情况):
trustmanager[] trustallcerts = new trustmanager[] {
new x509trustmanager() {
public java.security.cert.x509certificate[] getacceptedissuers() {
return null;
}
public void checkclienttrusted(
java.security.cert.x509certificate[] certs, string authtype) {
}
public void checkservertrusted(
java.security.cert.x509certificate[] certs, string authtype) {
}
}
};
sslcontext sc = sslcontext.getinstance("ssl");
sc.init(null, trustallcerts, new java.security.securerandom());
httpsurlconnection.setdefaultsslsocketfactory(sc.getsocketfactory());重要警告:这种方法会完全禁用ssl验证,存在严重安全风险。只应在测试或内部可信网络中使用,生产环境强烈建议使用正规证书。
5. 常见问题排查技巧
在实际运维中,我总结了一些排查这类问题的实用技巧:
5.1 证书链完整性检查
有时候问题不在终端证书,而在中间证书。用这个命令检查完整链:
openssl verify -cafile ca-bundle.crt your-certificate.crt
如果显示"ok"表示链完整,否则需要补充缺失的中间证书。
5.2 不同java版本的差异
注意jdk版本间的行为差异:
- jdk 7及更早:主要检查cn字段
- jdk 8:开始强制检查sans
- jdk 11+:对sans的检查更加严格
可以用以下代码检查当前jvm使用的安全策略:
system.out.println("security providers:");
for (provider p : security.getproviders()) {
system.out.println(p.getname());
}5.3 浏览器与java的不同表现
经常有开发者困惑:"为什么浏览器访问正常,java程序就报错?"这是因为:
- 现代浏览器会回退到cn字段验证
- 浏览器会缓存中间证书,而java可能需要完整链
- 浏览器有更宽松的证书过期处理机制
这种差异正是ssl/tls实现碎片化的典型表现。
到此这篇关于java ssl证书错误:no subject alternative names present的解决方案的文章就介绍到这了,更多相关java ssl证书错误内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论