我有一个Java Web服务客户端,它通过HTTPS使用Web服务。
import javax.xml.ws.Service;
@WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
public class ISomeService
extends Service
{
public ISomeService() {
super(__getWsdlLocation(), ISOMESERVICE_QNAME);
}
当我连接到服务URL(https://AAA.BBB.CCC.DDD:9443/ISomeService
)时,出现异常java.security.cert.CertificateException: No subject alternative names present
。
要解决此问题,我首先运行openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt
并在file中得到以下内容certs.txt
:
CONNECTED(00000003)
---
Certificate chain
0 s:/CN=someSubdomain.someorganisation.com
i:/CN=someSubdomain.someorganisation.com
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=someSubdomain.someorganisation.com
issuer=/CN=someSubdomain.someorganisation.com
---
No client certificate CA names sent
---
SSL handshake has read 489 bytes and written 236 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 512 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : RC4-MD5
Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Session-ID-ctx:
Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Key-Arg : None
Start Time: 1382521838
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
AFAIK,现在我需要
- 提取的部分
certs.txt
之间-----BEGIN CERTIFICATE-----
和-----END CERTIFICATE-----
, - 对其进行修改,以使证书名称等于
AAA.BBB.CCC.DDD
和 - 然后使用导入结果
keytool -importcert -file fileWithModifiedCertificate
(fileWithModifiedCertificate
操作1和2的结果在哪里)。
它是否正确?
如果是这样,我如何才能使第1步中的证书与基于IP的地址(AAA.BBB.CCC.DDD
)一起使用?
更新1( 2013年10月23日15:37 MSK):在回答类似问题时,我阅读以下内容:
如果您不受该服务器的控制,请使用其主机名(前提是在现有证书中至少有一个与该主机名匹配的CN)。
“使用”到底是什么意思?
我将以下代码放入ISomeService
该类中:
static {
disableSslVerification();
}
private static void disableSslVerification() {
try
{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
由于我https://AAA.BBB.CCC.DDD:9443/ISomeService
仅将用作测试目的,所以这是一个足够好的解决方案。
我有同样的问题,并用这段代码解决了。我将此代码放在第一次调用我的Web服务之前。
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
return hostname.equals("localhost");
}
});
这很简单,而且效果很好。
这是原始来源。
这是一个老问题,但是从JDK 1.8.0_144移至jdk 1.8.0_191时,我遇到了同样的问题
我们在变更日志中找到了一个提示:
我们添加了以下附加系统属性,在我们的案例中有助于解决此问题:
-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true
证书身份的验证是根据客户端的请求执行的。
当您的客户端使用https://xxx.xxx.xxx.xxx/something
(其中xxx.xxx.xxx.xxx
是IP地址)时,将根据该IP地址检查证书身份(理论上仅使用IP SAN扩展名)。
如果您的证书没有IP SAN,但是有DNS SAN(或者如果没有DNS SAN,则在主题DN中为公用名),则可以通过使客户端使用带有该主机名(或主机名)的URL来使其工作如果有多个可能的值,证书将对其有效。例如,如果您拥有的名称www.example.com
,请使用https://www.example.com/something
。
当然,您需要该主机名才能解析为该IP地址。
此外,如果有任何DNS SAN,则将忽略“主题DN”中的CN,因此在这种情况下,请使用与其中一个DNS SAN匹配的名称。
导入证书:
- 从服务器
openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt
提取证书,例如,这将提取PEM格式的证书。 - 将证书转换为DER格式,因为这是keytool期望的,例如
openssl x509 -in certs.txt -out certs.der -outform DER
- 现在,您想将此证书导入系统默认的“ cacert”文件。找到Java安装的系统默认“证书”文件。看一下如何获取默认java安装的cacerts的位置?
- 将证书导入到该cacerts文件中:
sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>
默认的cacerts密码为“ changeit”。
如果证书是针对FQDN颁发的,而您正尝试通过Java代码中的IP地址进行连接,则这可能应该在代码中得到解决,而不是弄乱证书本身。更改您的代码以通过FQDN连接。如果您的开发机器上无法解析FQDN,只需将其添加到您的主机文件中,或使用可以解析此FQDN的DNS服务器配置您的机器。
我以正确的方式解决了此问题,方法是在证书中添加主题替代名称,而不是像在此其他答案所建议的那样对代码进行任何更改或禁用SSL。如果您清楚地看到该异常,则说明“主题alt名称丢失”,因此正确的方法应该是添加它们
请查看此链接以逐步了解。
上面的错误表示您的JKS文件缺少尝试访问该应用程序所需的域。您将需要使用Open SSL和密钥工具来添加多个域
- 将openssl.cnf复制到当前目录
echo '[ subject_alt_name ]' >> openssl.cnf
echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/emailAddress=postmaster@example.com' -days 365
-
将公钥(.pem)文件导出为PKS12格式。这将提示您输入密码
openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in self-signed.pem -inkey private.key -name myalias -out keystore.p12
-
从自签名PEM(密钥库)创建a.JKS
keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
-
从密钥库或JKS文件生成证书
keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
-
由于上述证书是自签名的,并且未经CA验证,因此需要将其添加到Truststore中(对于Windows,对于MAC,在以下位置的CAcerts文件中,请找到您的JDK的安装位置。)
sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
原始答案发布在此链接上。
您可能不希望禁用所有ssl验证,因此您可以通过此方法禁用hostName验证,这比其他方法要容易一些:
HttpsURLConnection.setDefaultHostnameVerifier(
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
[编辑]
正如conapart3所提到的,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
现在已弃用,因此可能会在以后的版本中将其删除,因此您将来可能会被迫推出自己的版本,尽管我仍会说我会避免使用所有已关闭所有验证功能的解决方案。
使用完整网址“ qatest.ourCompany.com/webService”而不是仅使用“ qatest / webService”解决了我遇到此错误的问题。原因是我们的安全证书具有通配符,即“ * .ourCompany.com”。一旦我输入了完整的地址,例外就消失了。希望这可以帮助。
已经在https://stackoverflow.com/a/53491151/1909708中回答了它。
这将失败,因为证书的通用名称(CN
在certification中Subject
)或任何其他名称(Subject Alternative Name
在证书中)都与目标主机名或IP地址不匹配。
例如,从JVM,当尝试连接到IP地址(WW.XX.YY.ZZ
)而不是DNS名称(https://stackoverflow.com)时,HTTPS连接将失败,因为存储在java truststore中的证书cacerts
需要通用名称(或证书替代名称,例如stackexchange.com或* .stackoverflow.com等)以匹配目标地址。
请检查:https : //docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier
HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
urlConnection.setSSLSocketFactory(socketFactory());
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setUseCaches(false);
urlConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
});
urlConnection.getOutputStream();
上面,传递了一个HostnameVerifier
始终返回的实现对象true
:
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
}
对于Spring Boot RestTemplate
:
- 添加
org.apache.httpcomponents.httpcore
依赖 -
使用
NoopHostnameVerifier
了SSL工厂:SSLContext sslContext = new SSLContextBuilder() .loadTrustMaterial(new URL("file:pathToServerKeyStore"), storePassword) // .loadKeyMaterial(new URL("file:pathToClientKeyStore"), storePassword, storePassword) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client); RestTemplate restTemplate = new RestTemplate(factory);
如果收到相同的错误消息,我就想到了这个问题。但是,在我的情况下,我们有两个URL,它们具有不同的子域(http://example1.xxx.com/someservice和http://example2.yyy.com/someservice),这些URL指向同一服务器。该服务器的* .xxx.com域只有一个通配符证书。通过第二个域使用服务时,找到的证书(* .xxx.com)与请求的域(* .yyy.com)不匹配,并且发生错误。
在这种情况下,我们不应尝试通过降低SSL安全性来修复此类错误消息,而应检查服务器及其上的证书。
public class RESTfulClientSSL {
static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}};
public class NullHostNameVerifier implements HostnameVerifier {
/*
* (non-Javadoc)
*
* @see javax.net.ssl.HostnameVerifier#verify(java.lang.String,
* javax.net.ssl.SSLSession)
*/
@Override
public boolean verify(String arg0, SSLSession arg1) {
// TODO Auto-generated method stub
return true;
}
}
public static void main(String[] args) {
HttpURLConnection connection = null;
try {
HttpsURLConnection.setDefaultHostnameVerifier(new RESTfulwalkthroughCer().new NullHostNameVerifier());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
String uriString = "https://172.20.20.12:9443/rest/hr/exposed/service";
URL url = new URL(uriString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//connection.setRequestMethod("POST");
BASE64Encoder encoder = new BASE64Encoder();
String username = "admin";
String password = "admin";
String encodedCredential = encoder.encode((username + ":" + password).getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedCredential);
connection.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
StringBuffer stringBuffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
stringBuffer.append(line);
}
String content = stringBuffer.toString();
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
我在springboot中经历了2种方式的SSL。我已经将所有正确的配置服务配置为tomcat服务器和服务调用方RestTemplate。但我收到错误消息“ java.security.cert.CertificateException:没有使用者替代名称”
通过解决方案后,我发现JVM需要此证书,否则会出现握手错误。
现在,如何将其添加到JVM。
转到jre / lib / security / cacerts文件。我们需要将服务器证书文件添加到jvm的cacerts文件中。
通过Windows中的命令行将服务器证书添加到cacerts文件的命令。
C:\ Program Files \ Java \ jdk1.8.0_191 \ jre \ lib \ security> keytool -import -noprompt -trustcacerts -alias sslserver -file E:\ spring_cloud_sachin \ ssl_keys \ sslserver.cer -keystore cacerts -storepass changeit
检查服务器证书是否已安装:
C:\ Program Files \ Java \ jdk1.8.0_191 \ jre \ lib \ security> keytool -list -keystore cacerts
您可以看到已安装证书的列表:
有关更多详细信息:https : //sachin4java.blogspot.com/2019/08/javasecuritycertcertificateexception-no.html
在证书中添加主机条目以及与CN对应的ip
CN = someSubdomain.someorganisation.com
现在,使用您尝试访问URL的CN名称更新ip。
它为我工作。
此代码将像charm一样工作,并将restTemple对象用于其余代码。
RestTemplate restTemplate = new RestTemplate();
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate.setRequestFactory(requestFactory);
}
当您同时具有CN和使用者备用名称(SAN)的证书时,如果您基于CN内容提出请求,则该特定内容也必须存在于SAN下,否则它将因相关错误而失败。
就我而言,CN有一些东西,SAN还有其他东西。我必须使用SAN URL,然后它才能正常工作。
我已经解决了
MqttException(0)-javax.net.ssl.SSLHandshakeException:证书上的subjectAltNames不匹配
通过在服务器证书(具有CN = example.com)中添加一个(可以添加多个)替代主题名称来产生错误,该证书名称随后打印部分证书,如下所示:
Subject Alternative Name:
DNS: example.com
我在Windows上使用KeyExplorer生成服务器证书。您可以单击此链接来添加备用主题名称(紧随其后的是添加名称)。
我还面临着带有自签名证书的相同问题。通过参考上述几种解决方案,我尝试使用正确的CN(即服务器的IP地址)重新生成证书。但是对于我来说仍然无效。最后,我尝试通过以下提到的命令向其添加SAN地址来重新生成证书
**keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -alias <IP_ADDRESS> -ext san=ip:<IP_ADDRESS>**
之后,我启动服务器并通过以下提到的openssl命令下载了客户端证书
**openssl s_client -showcerts -connect <IP_ADDRESS>:443 < /dev/null | openssl x509 -outform PEM > myCert.pem**
然后我通过以下提到的命令将此客户端证书导入到我的客户端计算机的Java默认密钥库文件(证书)中
**keytool -import -trustcacerts -keystore /home/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/cacerts -alias <IP_ADDRESS> -file ./mycert.pem**
将IP地址添加到hosts文件中。该文件位于C:\ Windows \ System32 \ drivers \ etc文件夹中。还要添加IP和IP地址的域名。例如:aaa.bbb.ccc.ddd abc@def.com
我已经通过以下方式解决了这个问题。
1.创建一个类。该类有一些空的实现
class MyTrustManager implements X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
throws CertificateException {
// TODO Auto-generated method stub
}
2.创建方法
private static void disableSSL() {
try {
TrustManager[] trustAllCerts = new TrustManager[] { new MyTrustManager() };
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
- 在抛出异常的地方调用disableSSL()方法。工作正常。
文章标签:certificate , https , java , ssl , ssl-certificate
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!
评论已关闭!