我最近将LetsEncrypt证书添加到我的服务器,我的java applet在使用TLS连接时出现问题。
我的applet使用Apache HttpClient。
我的网络服务器是Apache 2,4,我有几个虚拟主机设置为我的主域的子域(foo.com - 不是我的真实域名)。
当我在暂存子域上运行我的applet时(例如它运行https://staging.foo.com),我收到以下错误:
javax.net.ssl.SSLException: Certificate for <staging.foo.com> doesn't match any of the subject alternative names: [developer.foo.com]
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:165)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:61)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:141)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:114)
at org.apache.http.conn.ssl.SSLSocketFactory.verifyHostname(SSLSocketFactory.java:580)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:554)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:412)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:179)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:328)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:612)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:447)
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:884)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
...(cut)
at javax.swing.SwingWorker$1.call(SwingWorker.java:295)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at javax.swing.SwingWorker.run(SwingWorker.java:334)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
我不知道发生了什么。
首先,我不知道Java是如何知道developer.foo.bar是我的虚拟主机之一(尽管这个虚拟主机是第一个按字母顺序打开SSL的主机)。
我查看了staging.foo.com的证书详细信息,“主题备用名称”字段下列出的唯一名称是staging.foo.com。
那么从哪里获取developer.foo.com?
我该如何解决这个问题?
我在OS X El Capitan 10.11.6上使用Firefox,带有以下Java插件版本信息:
Java Plug-in 11.102.2.14 x86_64
Using JRE version 1.8.0_102-b14 Java HotSpot(TM) 64-Bit Server VM
这是staging.foo.com的Apache conf文件:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName staging.foo.com
ServerAlias www.staging.foo.com
# Turn on HTTP Strict Transport Security (HSTS). This tells the
# client that it should only communicate with this site using
# HTTPS. See
# https://raymii.org/s/tutorials/HTTP_Strict_Transport_Security_for_Apache_NGINX_and_Lighttpd.html
Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains;"
# The following is used to tunnel websocket requests to daphne, so
# that Django Channels can do its thing
ProxyPass "/ws/" "ws://localhost:8001/ws/"
ProxyPassReverse "/ws/" "ws://localhost:8001/ws/"
# The following is used during deployment. Every page request is
# served from one static html file.
RewriteEngine on
RewriteCond /home/www-mm/staging.foo.com/apache/in_maintenance -f
RewriteRule .* /home/www-mm/staging.foo.com/static/maintenance/maintenance.html
# Use Apache to serve protected (non-static) files. This is so that
# Apache can deal with ranges
XSendFile on
XSendFilePath /home/www-mm/staging.foo.com/user_assets
# Limit uploads - 200MB
LimitRequestBody 209715200
Alias /static/ /home/www-mm/staging.foo.com/serve_static/
Alias /robots.txt /home/www-mm/staging.foo.com/apache/serve-at-root/robots.txt
<Directory /home/www-mm/staging.foo.com/serve_static>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json
Order deny,allow
Require all granted
</Directory>
# Videos uploaded via staff to home page should never cache,
# because they can change at any time (and we don't know if the
# URLs will change or not). Etags are used and only headers are
# sent if the files in question aren't modified (we get a 304
# back)
<Directory /home/www-mm/staging.foo.com/serve_static/video>
ExpiresActive On
# Expire immediately
ExpiresDefault A0
</Directory>
# The following ensures that the maintenance page is never cached.
<Directory /home/www-mm/staging.foo.com/static/maintenance>
ExpiresActive On
# Expire immediately
ExpiresDefault A0
Require all granted
</Directory>
# Hide uncompressed code from prying eyes. Python needs access to this code for the css compressor
<Directory /home/www-mm/staging.foo.com/serve_static/js/muso>
<Files ~ "\.js$">
Deny from all
</Files>
# Order deny,allow
# Deny from all
</Directory>
# Hide uncompressed code from prying eyes. Python needs access to this code for the css compressor
<DirectoryMatch "/home/www-mm/staging.foo.com/serve_static/js/dist/.*/muso">
Order deny,allow
Deny from all
</DirectoryMatch>
<Directory /home/www-mm/staging.foo.com/apache>
<Files django.wsgi>
Order deny,allow
Require all granted
</Files>
</Directory>
WSGIScriptAlias / /home/www-mm/staging.foo.com/apache/django.wsgi
WSGIDaemonProcess staging.foo.com user=www-mm group=www-mm
WSGIProcessGroup staging.foo.com
ErrorLog /var/log/apache2/staging.foo.com-error.log
CustomLog /var/log/apache2/staging.foo.com-access.log combined
SSLCertificateFile /etc/letsencrypt/live/staging.foo.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/staging.foo.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
SSL部分由certbot(LetsEncrypt CLI工具)添加。
我应该补充说,在现代浏览器(例如Chrome)中访问每个子域名都可以。
答案 0 :(得分:2)
如果您使用HttpClient 4.4,则需要指定主机验证程序(NoopHostnameVerifier)以允许接受来自不同主机的证书:
SSLConnectionSocketFactory scsf = SSLConnectionSocketFactory(
SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
NoopHostnameVerifier.INSTANCE)
httpclient = HttpClients.custom().setSSLSocketFactory(scsf).build()
答案 1 :(得分:1)
我不知道您使用的是哪个版本的Apache HttpClient,但版本4.4.1和4.5.1有一个SNI无法正常工作的错误。这在4.5.3中得到修复
答案 2 :(得分:0)
当我使用org.apache.http。*中的方法来发出我的http请求时,我遇到了同样的错误。从你的堆栈跟踪我认为即使你使用相同的。
当我使用java.net.HttpURLConnection并且我能够成功连接时,此错误消失。
import java.net.HttpURLConnection;
public static HttpURLConnection connectToWeb(String uri) {
HttpURLConnection connection = null;
try {
URL url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
} catch (MalformedURLException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
return connection;
}
答案 3 :(得分:0)
按照Yurri的评论,它通过初始化SSLConnectionSocketFactory时添加NoopHostnameVerifier.INSTANCE解决了我的问题:
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
/**
* Provide basic Utils for getting HttpHeader and making REST api calls.
*
*/
@Component
public class HttpUtil {
private static final Logger LOG = LoggerFactory.getLogger(HttpUtil.class);
/**
* The default implementation to get basic headers.
* @return HttpHeaders.
*/
public HttpHeaders getHttpHeaders(String userAgent, String host) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
headers.set(HttpHeaders.USER_AGENT, userAgent);
LOG.info("host=" + host);
if (null != host) {
headers.set(HttpHeaders.HOST, host);
}
return headers;
}
/**
* Default implementation to get RestTemplate
* @return
*/
public RestTemplate getRestTemplate(String proxyHost, int proxyPort)
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = new TrustSelfSignedStrategy();
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
if (null != proxyHost && proxyPort > 0) {
LOG.info("PROXY CONFIGURED | proxyHost=" + proxyHost + " | proxyPort=" + proxyPort);
HttpHost proxy = new HttpHost(proxyHost, proxyPort, Proxy.Type.HTTP.name());
httpClient = HttpClients.custom().setSSLSocketFactory(csf)
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)).build();
}
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
/**
* Make a rest api call
* @return ResponseEntity
*/
public ResponseEntity<String> getApiResponse(HttpMethod httpMethod, final String URL, final String userAgent,
String proxyHost, int proxyPort, String host) throws HttpClientErrorException {
ResponseEntity<String> response = null;
HttpEntity<String> httpEntity = new HttpEntity<>(getHttpHeaders(userAgent, host));
try {
if (null != httpMethod && null != URL) {
RestTemplate request = null;
try {
request = getRestTemplate(proxyHost, proxyPort);
response = request.exchange(URL, httpMethod, httpEntity, String.class);
} catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
LOG.error("Error creating Rest Template", e);
}
}
} catch (HttpClientErrorException ex) {
LOG.error("Method = " + httpMethod.toString() + "Request URL = " + URL);
LOG.error("Headers =" + getHttpHeaders(userAgent, host));
LOG.error("Response Status = " + ex.getStatusText());
LOG.error("Response Body = " + ex.getResponseBodyAsString());
throw ex;
}
return response;
}
}
答案 4 :(得分:0)
如果您尝试访问任何对象中的 URL,请尝试在代码中设置以下内容(取决于您尝试访问 URL 的方式,例如。这里我们使用 WebClient 对象设置以下参数)
创建 WebClient 对象和设置以下内容:- WebClient webClient = null;
System.setProperty("jsse.enableSNIExtension", "false");
根据您的 WebClient 版本设置以下内容。webClient.getOptions().setUseInsecureSSL(true);
答案 5 :(得分:-1)
/*
* Inner class for Proxy SSL Socket Connection.
*/
static class MyConnectionSocketFactory extends SSLConnectionSocketFactory {
private String proxyHost = null;
private Integer proxyPort = null;
public MyConnectionSocketFactory(final SSLContext sslContext, String proxyHost, Integer proxyPort) {
super(sslContext, new NoopHostnameVerifier());
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
}
@Override
public Socket createSocket(final HttpContext context) throws IOException {
logger.debug("Create Socket:" + " ProxyHost: " + proxyHost + ", ProxyPort: " + proxyPort);
InetSocketAddress socksaddr = new InetSocketAddress(proxyHost,proxyPort);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
return new Socket(proxy);
}
}
else if (proxyType.equalsIgnoreCase("socks")) {
logger.debug("Proxy Type: " + proxyType);
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new MyConnectionSocketFactory(sslContext, proxyHost, proxyPort);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate = new RestTemplate(requestFactory);
return;
} else {