一文解決Java中IP地址訪問HTTPS接口的SSL證書驗(yàn)證問題
問題描述
在使用Java的RestTemplate訪問HTTPS接口時(shí),如果使用IP地址而不是域名,經(jīng)常會(huì)遇到以下錯(cuò)誤:
java.security.cert.CertificateException: No subject alternative names matching IP address 111.63.81.79 found
nested exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address 111.63.81.79 found
奇怪的是:使用Postman、curl等工具可以正常訪問,但Java代碼卻報(bào)錯(cuò)。
問題原因
SSL證書驗(yàn)證機(jī)制
HTTPS協(xié)議在建立連接時(shí),會(huì)進(jìn)行SSL證書驗(yàn)證,主要包括兩個(gè)方面:
- 證書有效性驗(yàn)證:檢查證書是否過期、是否被吊銷等
- 主機(jī)名驗(yàn)證:檢查證書中的域名或IP地址是否與訪問的地址匹配
為什么Postman可以,Java不行
- Postman:默認(rèn)情況下,Postman可能會(huì)跳過某些SSL驗(yàn)證(特別是開發(fā)環(huán)境)
- Java:Java的SSL驗(yàn)證非常嚴(yán)格,會(huì)檢查證書的
Subject Alternative Names (SAN)字段 - 問題根源:當(dāng)使用IP地址訪問時(shí),如果證書的SAN中沒有包含該IP地址,Java就會(huì)拒絕連接
證書的Subject Alternative Names
SSL證書中有一個(gè)字段叫Subject Alternative Names (SAN),它列出了該證書可以用于哪些域名或IP地址。例如:
Subject Alternative Names:
DNS: example.com
DNS: www.example.com
IP Address: 192.168.1.1
如果證書中沒有包含你訪問的IP地址(如111.63.81.79),Java就會(huì)拋出上述異常。
解決方案
方案概述
我們需要配置RestTemplate,讓它跳過SSL證書驗(yàn)證。注意:這種方法只適用于開發(fā)/測(cè)試環(huán)境,生產(chǎn)環(huán)境應(yīng)該使用正確的證書。
完整代碼實(shí)現(xiàn)
創(chuàng)建一個(gè)RestTemplateConfig配置類:
package com.example.zbx.config;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
/**
* RestTemplate配置類
* 配置忽略SSL證書驗(yàn)證,支持使用IP地址訪問HTTPS接口
* @author lxy
*/
@Configuration
public class RestTemplateConfig {
/**
* 創(chuàng)建RestTemplate Bean
* 配置忽略SSL證書驗(yàn)證,支持使用IP地址訪問HTTPS接口
* @return RestTemplate實(shí)例
*/
@Bean
public RestTemplate restTemplate() {
try {
// 創(chuàng)建完全信任所有證書的TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// 不進(jìn)行任何檢查,信任所有客戶端證書
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// 不進(jìn)行任何檢查,信任所有服務(wù)器證書
}
}
};
// 創(chuàng)建SSL上下文,使用自定義的TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// 創(chuàng)建SSL連接工廠,忽略主機(jī)名驗(yàn)證(包括IP地址驗(yàn)證)
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE
);
// 創(chuàng)建請(qǐng)求配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(10000) // 連接超時(shí)10秒
.setConnectionRequestTimeout(10000) // 請(qǐng)求超時(shí)10秒
.setSocketTimeout(30000) // 讀取超時(shí)30秒
.build();
// 創(chuàng)建HttpClient
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.setDefaultRequestConfig(requestConfig)
.evictExpiredConnections()
.evictIdleConnections(30, java.util.concurrent.TimeUnit.SECONDS)
.build();
// 創(chuàng)建請(qǐng)求工廠
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(10000);
factory.setConnectionRequestTimeout(10000);
factory.setReadTimeout(30000);
// 創(chuàng)建RestTemplate
return new RestTemplate(factory);
} catch (Exception e) {
throw new RuntimeException("創(chuàng)建RestTemplate失敗", e);
}
}
}
關(guān)鍵點(diǎn)說明
1. 自定義TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// 不進(jìn)行任何檢查,信任所有服務(wù)器證書
}
// ... 其他方法
}
};
這個(gè)X509TrustManager會(huì)跳過所有證書驗(yàn)證,包括:
- 證書是否過期
- 證書是否被吊銷
- 證書的域名/IP是否匹配
2. 創(chuàng)建SSL上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
使用自定義的TrustManager初始化SSL上下文。
3. 忽略主機(jī)名驗(yàn)證
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE // 不驗(yàn)證主機(jī)名
);
NoopHostnameVerifier.INSTANCE會(huì)跳過主機(jī)名驗(yàn)證,這樣即使證書中沒有對(duì)應(yīng)的IP地址也能通過驗(yàn)證。
Maven依賴
確保你的pom.xml中包含以下依賴:
<!-- HttpClient4 (for RestTemplate SSL configuration) -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
使用示例
配置完成后,直接使用RestTemplate即可,無需額外代碼:
@Autowired
private RestTemplate restTemplate;
public void callApi() {
String url = "https://111.63.81.79:10091/api/endpoint";
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
// 處理響應(yīng)...
}
注意事項(xiàng)
安全警告
- 僅用于開發(fā)/測(cè)試環(huán)境:跳過SSL驗(yàn)證會(huì)帶來安全風(fēng)險(xiǎn),生產(chǎn)環(huán)境不應(yīng)該使用
- 中間人攻擊風(fēng)險(xiǎn):攻擊者可以偽造證書,你的應(yīng)用無法識(shí)別
- 數(shù)據(jù)泄露風(fēng)險(xiǎn):無法保證連接的安全性
生產(chǎn)環(huán)境建議
如果必須在生產(chǎn)環(huán)境使用,建議:
- 使用正確的證書:讓服務(wù)器提供包含IP地址的證書
- 配置證書信任:只信任特定的證書頒發(fā)機(jī)構(gòu)(CA)
- 使用域名訪問:盡量使用域名而不是IP地址
示例:生產(chǎn)環(huán)境的正確做法
// 只信任特定的證書
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("truststore.jks"), "password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
常見問題
Q1: 為什么之前可以,現(xiàn)在不行了?
可能的原因:
- Java版本更新,SSL驗(yàn)證更嚴(yán)格了
- 服務(wù)器證書更新了,新證書沒有包含IP地址
- 網(wǎng)絡(luò)環(huán)境變化,使用了不同的IP地址
Q2: 有沒有更簡單的辦法?
對(duì)于開發(fā)環(huán)境,可以設(shè)置JVM參數(shù)(不推薦):
-Dcom.sun.net.ssl.checkRevocation=false
但這種方法不夠靈活,建議使用配置類的方式。
Q3: 會(huì)影響其他HTTP請(qǐng)求嗎?
不會(huì)。這個(gè)配置只影響使用RestTemplate的HTTPS請(qǐng)求,HTTP請(qǐng)求不受影響。
Q4: 如何驗(yàn)證配置是否生效?
在代碼中添加日志,觀察是否還有證書驗(yàn)證錯(cuò)誤:
log.info("RestTemplate配置完成,SSL驗(yàn)證已禁用");
如果不再出現(xiàn)CertificateException,說明配置生效了。
總結(jié)
- 問題根源:Java的SSL驗(yàn)證嚴(yán)格,證書中沒有IP地址時(shí)會(huì)拒絕連接
- 解決方案:配置
RestTemplate跳過SSL證書驗(yàn)證 - 關(guān)鍵代碼:自定義
X509TrustManager+NoopHostnameVerifier - 安全提醒:僅用于開發(fā)/測(cè)試環(huán)境,生產(chǎn)環(huán)境應(yīng)使用正確的證書
到此這篇關(guān)于一文解決Java中IP地址訪問HTTPS接口的SSL證書驗(yàn)證問題的文章就介紹到這了,更多相關(guān)Java解決SSL證書驗(yàn)證問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis遷移到Mybatis-Plus的實(shí)現(xiàn)方法
這篇文章主要介紹了Mybatis遷移到Mybatis-Plus的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
如何基于LoadingCache實(shí)現(xiàn)Java本地緩存
這篇文章主要介紹了如何基于LoadingCache實(shí)現(xiàn)Java本地緩存,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法
這篇文章主要介紹了EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java中的static關(guān)鍵字用法總結(jié)
這篇文章主要介紹了Java中的static關(guān)鍵字用法總結(jié),static是Java50個(gè)關(guān)鍵字之一,static關(guān)鍵字可以用來修飾代碼塊表示靜態(tài)代碼塊,修飾成員變量表示全局靜態(tài)成員變量,修飾方法表示靜態(tài)方法,需要的朋友可以參考下2023-11-11

