从Java应用程序调用https://outlook.office365.com/EWS/Exchange.asmx时随



我有一个小应用程序Java 11,它运行在一个容器中,并在线连接到https://outlook.office365.com/EWS/Exchange.asmx上的Exchange。我正在使用OKHttp3 (v4.9.1)连接到它。

大多数时候,它是有效的。但有时,它会失败,除了以下例外:

原因:javax.net.ssl.SSLHandshakeException: Remote host terminated握手java.base/sun.security.ssl.SSLSocketImpl.handleEOF (SSLSocketImpl.java: 1321)在java.base/sun.security.ssl.SSLSocketImpl.decode (SSLSocketImpl.java: 1160)在java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord (SSLSocketImpl.java: 1063)在java.base/sun.security.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.java: 402)在okhttp3.internal.connection.RealConnection.connectTls (RealConnection.kt: 379)okhttp3.internal.connection.RealConnection.establishProtocol (RealConnection.kt: 337)在okhttp3.internal.connection.RealConnection.connect (RealConnection.kt: 209)在okhttp3.internal.connection.ExchangeFinder.findConnection (ExchangeFinder.kt: 226)在okhttp3.internal.connection.ExchangeFinder.findHealthyConnection (ExchangeFinder.kt: 106)在okhttp3.internal.connection.ExchangeFinder.find (ExchangeFinder.kt: 74)在okhttp3.internal.connection.RealCall.initExchange okhttp美元(RealCall.kt: 255)在okhttp3.internal.connection.ConnectInterceptor.intercept (ConnectInterceptor.kt: 32)在okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt: 109)在okhttp3.internal.cache.CacheInterceptor.intercept (CacheInterceptor.kt: 95)在okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt: 109)在okhttp3.internal.http.BridgeInterceptor.intercept (BridgeInterceptor.kt: 83)在okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt: 109)在okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept (RetryAndFollowUpInterceptor.kt: 76)在okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt: 109)在okhttp3.logging.HttpLoggingInterceptor.intercept (HttpLoggingInterceptor.kt: 221)在okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt: 109)在okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain okhttp美元(RealCall.kt: 201)realcall .execute(RealCall.kt:154vincent.email.kpi.tracker.o365.MyHttpClient.send (MyHttpClient.java: 47)在com.microsoft.aad.msal4j.HttpHelper.executeHttpRequestWithRetries (HttpHelper.java: 86)在com.microsoft.aad.msal4j.HttpHelper.executeHttpRequest (HttpHelper.java: 64)... 7常见帧省略原因:java.io.EOFException: SSL peer错误关闭java.base/sun.security.ssl.SSLSocketInputRecord.decode (SSLSocketInputRecord.java: 167)在java.base/sun.security.ssl.SSLTransport.decode (SSLTransport.java: 108)在java.base/sun.security.ssl.SSLSocketImpl.decode (SSLSocketImpl.java: 1152)... 31个常用框架省略

它随机失败的事实让我很困惑……它运行在同一个Docker映像中,所以我这边的配置和证书不会从一次运行到另一次运行而改变。我希望在office365服务器上也是如此。

有什么我可以添加的地方使它更"确定性"吗?

?下面是我的代码:

import com.microsoft.aad.msal4j.HttpMethod;
import com.microsoft.aad.msal4j.HttpRequest;
import com.microsoft.aad.msal4j.HttpResponse;
import com.microsoft.aad.msal4j.IHttpResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Credentials;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
public class MyHttpClient implements com.microsoft.aad.msal4j.IHttpClient {
public static final String PROXY_HOST = "my.proxy.url";
public static final int PROXY_PORT = 8080;
private final OkHttpClient client;
public MyHttpClient(String authUser, String authPassword){
this.client = buildHttpClient(authUser,authPassword);
}
@Override
public IHttpResponse send(HttpRequest httpRequest) throws IOException {
// Map URL, headers, and body from MSAL's HttpRequest to OkHttpClient request object
var request = buildOkRequestFromMsalRequest(httpRequest);
// Execute Http request with OkHttpClient
var okHttpResponse= client.newCall(request).execute();
// Map status code, headers, and response body from OkHttpClient's Response object to MSAL's IHttpResponse
return buildMsalResponseFromOkResponse(okHttpResponse);
}
private static IHttpResponse buildMsalResponseFromOkResponse(Response okHttpResponse) throws IOException {
var msal4j = new HttpResponse();
msal4j.statusCode(okHttpResponse.code());
for (String headerKey : okHttpResponse.headers().names()) {
List<String> val = okHttpResponse.headers(headerKey);
msal4j.headers().put(headerKey, val);
}
msal4j.body(okHttpResponse.body().string());
return msal4j;
}
private static Request buildOkRequestFromMsalRequest(HttpRequest httpRequest) {
var builder=new Request.Builder()
.url(httpRequest.url());
if(httpRequest.httpMethod()== HttpMethod.POST) {
builder.method("POST", RequestBody.create(httpRequest.body().getBytes(StandardCharsets.UTF_8)));
}
//defaults to GET, with no body

if(httpRequest.headers()!=null){
builder.headers(Headers.of(httpRequest.headers()));
}
return builder.build();
}

private static OkHttpClient buildHttpClient(final String authUser, final String authPassword) {
okhttp3.Authenticator proxyAuthenticator = new okhttp3.Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(authUser, authPassword);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
};
var logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BODY);
return new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
.proxyAuthenticator(proxyAuthenticator)
//TODO should no ignore certificates in prod..
// --> certificate should be added in store
.sslSocketFactory(trustAllSslSocketFactory, (X509TrustManager)trustAllCerts[0])
.addInterceptor(logging)
.hostnameVerifier((hostname, session) -> true)
.build();
}
private static final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
private static final SSLContext trustAllSslContext;
static {
try {
trustAllSslContext = SSLContext.getInstance("SSL");
trustAllSslContext.init(null, trustAllCerts, new java.security.SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new Office365Exception(e);
}
}
private static final SSLSocketFactory trustAllSslSocketFactory = trustAllSslContext.getSocketFactory();
}

我希望在office365服务器上也是如此。

我希望有成千上万的Office365服务器实例与负载平衡器(等等)在他们的前面。每次您的客户机尝试连接时,都可能与不同的SSL端点进行通信。它们的配置可能不尽相同。这可能是对非决定论的一种解释。

有什么我可以添加的地方,使它更"确定性"?

大概不会。

但是你可以(应该)做的是试着找出为什么远程服务器正在重置连接。

服务器在SSL连接握手期间重置连接通常表示SSL端点已决定无法建立安全连接。常见的原因包括无法就协议版本或使用的加密算法达成一致。(这通常发生在客户端或服务器端使用不再被认为是安全的SSL协议或加密算法时。)

诊断问题的标准方法是使用-Djavax.net.debug=all选项运行JVM。这将记录大量的信息,包括SSL协商的细节。您可以比较客户端和服务器"提供"的内容,然后找出不匹配的地方。

查看更多信息;

查看SSL/TLS连接调试。

最新更新