OkHttp는 자체 서명 된 SSL 인증서 수락을 지원합니까?
자체 서명 된 SSL 인증서가있는 서버를 보유한 고객을 위해 일하고 있습니다.
래핑 된 OkHttp 클라이언트를 사용하여 Retrofit + CustomClient를 사용하고 있습니다.
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
.setClient(new CustomClient(new OkClient(), context))
.build();
OkHttp는 기본적으로 자체 서명 된 SSL 인증서 서버 호출을 지원합니까?
그건 그렇고. 기본적으로 Retrofit을 사용하는 클라이언트는 무엇입니까? OkHttp라고 생각했지만 조금 더 조사했을 때 OkHttp 종속성을 가져와야한다는 것을 깨달았습니다.
네, 그렇습니다.
Retrofit을 사용하면 필요에 맞게 구성된 사용자 지정 HTTP 클라이언트를 설정할 수 있습니다.
자체 서명 된 SSL 인증서에 대한 논의가 여기에 있습니다 . 링크에는 자체 서명 된 SLL을 Android DefaultHttpClient
에 추가하고이 클라이언트를 Retrofit에로드하는 코드 샘플이 포함되어 있습니다 .
OkHttpClient
자체 서명 된 SSL을 수락 해야하는 경우 메서드 javax.net.ssl.SSLSocketFactory
를 통해 사용자 지정 인스턴스를 전달해야합니다 setSslSocketFactory(SSLSocketFactory sslSocketFactory)
.
소켓 팩토리를 얻는 가장 쉬운 방법 javax.net.ssl.SSLContext
은 여기 에서 논의한대로 소켓 팩토리를 얻는 것입니다 .
다음은 OkHttpClient를 구성하기위한 샘플입니다.
OkHttpClient client = new OkHttpClient();
KeyStore keyStore = readKeyStore(); //your method to obtain KeyStore
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "keystore_pass".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
client.setSslSocketFactory(sslContext.getSocketFactory());
okhttp3에 대한 업데이트 된 코드 (빌더 사용) :
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.build();
client
이곳은 지금으로부터 인증서를 사용하도록 구성되어 있습니다 KeyStore
. 그러나 KeyStore
시스템이 기본적으로 신뢰하더라도 사용자의 인증서 만 신뢰하고 다른 것은 신뢰하지 않습니다. (자체 서명 된 인증서 만 KeyStore
있고 HTTPS를 통해 Google 메인 페이지에 연결하려고하면 SSLHandshakeException
).
문서KeyStore
에서 볼 수 있듯이 파일에서 인스턴스를 얻을 수 있습니다 .
KeyStore readKeyStore() {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// get user password and file input stream
char[] password = getPassword();
java.io.FileInputStream fis = null;
try {
fis = new java.io.FileInputStream("keyStoreName");
ks.load(fis, password);
} finally {
if (fis != null) {
fis.close();
}
}
return ks;
}
Android를 사용 res/raw
하는 Context
경우 폴더에 넣고 다음을 사용하여 인스턴스 에서 가져올 수 있습니다.
fis = context.getResources().openRawResource(R.raw.your_keystore_filename);
키 저장소를 만드는 방법에 대한 몇 가지 논의가 있습니다. 예를 들어 여기
okhttp3.OkHttpClient 버전 com.squareup.okhttp3 : okhttp : 3.2.0의 경우 아래 코드를 사용해야합니다.
import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
......
OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
boolean allowUntrusted = true;
if ( allowUntrusted) {
Log.w(TAG,"**** Allow untrusted SSL connection ****");
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] cArrr = new X509Certificate[0];
return cArrr;
}
@Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
clientBuilder.sslSocketFactory(sslContext.getSocketFactory());
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
Log.d(TAG, "Trust Host :" + hostname);
return true;
}
};
clientBuilder.hostnameVerifier( hostnameVerifier);
}
final Call call = clientBuilder.build().newCall(request);
앱 에서 키 저장소에서 자체 서명 된 인증서를 인식하는 OkHttpClient 3.0 인스턴스 를 가져 오는 두 가지 방법 (Android 프로젝트 "원시" 리소스 폴더에 준비된 pkcs12 인증서 파일 사용 ) :
private static OkHttpClient getSSLClient(Context context) throws
NoSuchAlgorithmException,
KeyStoreException,
KeyManagementException,
CertificateException,
IOException {
OkHttpClient client;
SSLContext sslContext;
SSLSocketFactory sslSocketFactory;
TrustManager[] trustManagers;
TrustManagerFactory trustManagerFactory;
X509TrustManager trustManager;
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager) trustManagers[0];
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
sslSocketFactory = sslContext.getSocketFactory();
client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build();
return client;
}
/**
* Get keys store. Key file should be encrypted with pkcs12 standard. It can be done with standalone encrypting java applications like "keytool". File password is also required.
*
* @param context Activity or some other context.
* @return Keys store.
* @throws KeyStoreException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws IOException
*/
private static KeyStore readKeyStore(Context context) throws
KeyStoreException,
CertificateException,
NoSuchAlgorithmException,
IOException {
KeyStore keyStore;
char[] PASSWORD = "12345678".toCharArray();
ArrayList<InputStream> certificates;
int certificateIndex;
InputStream certificate;
certificates = new ArrayList<>();
certificates.add(context.getResources().openRawResource(R.raw.ssl_pkcs12));
keyStore = KeyStore.getInstance("pkcs12");
for (Certificate certificate : certificates) {
try {
keyStore.load(certificate, PASSWORD);
} finally {
if (certificate != null) {
certificate.close();
}
}
}
return keyStore;
}
Retrofit 1.9에 대해 다음 전략을 사용하여 인증서를 수락 할 수있었습니다. 위험을 감수하고 사용하십시오! 인증서를 수락하는 것은 위험하므로 결과를 이해해야합니다. 일부 관련 부품은에서 org.apache.http.ssl
가져 오므로 여기에서 일부 가져 오기가 필요할 수 있습니다.
// ...
Client httpClient = getHttpClient();
RestAdapter adapter = new RestAdapter.Builder()
.setClient(httpClient)
// ... the rest of your builder setup
.build();
// ...
private Client getHttpClient() {
try {
// Allow self-signed (and actually any) SSL certificate to be trusted in this context
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
sslContext.getSocketFactory();
SSLSocketFactory sf = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient();
client.setSslSocketFactory(sf);
return new OkClient(client);
} catch (Exception e) {
throw new RuntimeException("Failed to create new HTTP client", e);
}
}
이 게시물이 꽤 오래되었다는 것을 알고 3.12.1
있습니다. 제가 글을 쓰는 당시 버전 인 OkHttp의 최신 업데이트와 함께 저에게 도움이 된 솔루션을 공유하고 싶습니다 .
먼저 TrustManager에 추가 할 KeyStore 개체를 가져와야합니다.
/**
* @param context The Android context to be used for retrieving the keystore from raw resource
* @return the KeyStore read or null on error
*/
private static KeyStore readKeyStore(Context context) {
char[] password = "keystore_password".toCharArray();
// for non-android usage:
// try(FileInputStream is = new FileInputStream(keystoreName)) {
try(InputStream is = context.getResources().openRawResource(R.raw.keystore)) {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(is, password);
return ks;
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
이제 OkHttpClient
키 저장소에서 자체 서명 된 인증서를 사용하여 빌드 할 수 있습니다 .
/**
* @param context The Android context used to obtain the KeyStore
* @return the builded OkHttpClient or null on error
*/
public static OkHttpClient getOkHttpClient(Context context) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
return new OkHttpClient.Builder()
.hostnameVerifier((hostname, session) -> {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
/* Never return true without verifying the hostname, otherwise you will be vulnerable
to man in the middle attacks. */
return hv.verify("your_hostname_here", session);
})
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
기억 이 매우 권장하지 않습니다 에 항상 true 반환하는 hostnameVerifier
중앙 공격에 사람의 위험을 방지 할 수 있습니다.
나는 같은 문제가 있었고 다음과 같이 okhttp 클라이언트로 수정했습니다 .
1.) 다음 내용이 포함 된 certificate
파일을에 추가합니다 src/main/res/raw/
.
-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----
2.) okHttpClient를 인스턴스화합니다.
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSslContext(context).getSocketFactory())
.build();
3.) 사용 된 getSslContext(Context context)
방법 은 다음과 같습니다 .
SSLContext getSslContext(Context context) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS"
ks.load(null, null);
InputStream is = context.getResources().openRawResource(R.raw.certificate);
String certificate = Converter.convertStreamToString(is);
// generate input stream for certificate factory
InputStream stream = IOUtils.toInputStream(certificate);
// CertificateFactory
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// certificate
Certificate ca;
try {
ca = cf.generateCertificate(stream);
} finally {
is.close();
}
ks.setCertificateEntry("av-ca", ca);
// TrustManagerFactory
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
// Create a TrustManager that trusts the CAs in our KeyStore
tmf.init(ks);
// Create a SSLContext with the certificate that uses tmf (TrustManager)
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
SslContext에 여러 인증서를 추가해야하는 경우 해결 방법은 다음과 같습니다 .
다음 코드를 사용하면 Retrofit과 함께 사용할 수있는 OkHttp 클라이언트를 만들 수 있습니다. Mailmustdie의 대답은 더 안전하다는 점에서 "더 좋음"이지만 아래 코드 조각은 구현이 더 빠릅니다.
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okio.BufferedSink;
import retrofit.client.Header;
import retrofit.client.OkClient;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class TrustingOkClient extends OkClient {
static final int CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s
static final int READ_TIMEOUT_MILLIS = 20 * 1000; // 20s
private static OkHttpClient generateDefaultOkHttp() {
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
final TrustManager[] certs = new TrustManager[]{new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext ctx = null;
try {
ctx = SSLContext.getInstance("TLS");
ctx.init(null, certs, new SecureRandom());
} catch (final java.security.GeneralSecurityException ex) {
}
try {
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(final String hostname,
final SSLSession session) {
return true;
}
};
client.setHostnameVerifier(hostnameVerifier);
client.setSslSocketFactory(ctx.getSocketFactory());
} catch (final Exception e) {
}
return client;
}
private final OkHttpClient client;
public TrustingOkClient() {
this(generateDefaultOkHttp());
}
public TrustingOkClient(OkHttpClient client) {
if (client == null) throw new NullPointerException("client == null");
this.client = client;
}
@Override public Response execute(Request request) throws IOException {
return parseResponse(client.newCall(createRequest(request)).execute());
}
static com.squareup.okhttp.Request createRequest(Request request) {
com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder()
.url(request.getUrl())
.method(request.getMethod(), createRequestBody(request.getBody()));
List<Header> headers = request.getHeaders();
for (int i = 0, size = headers.size(); i < size; i++) {
Header header = headers.get(i);
String value = header.getValue();
if (value == null) value = "";
builder.addHeader(header.getName(), value);
}
return builder.build();
}
static Response parseResponse(com.squareup.okhttp.Response response) {
return new Response(response.request().urlString(), response.code(), response.message(),
createHeaders(response.headers()), createResponseBody(response.body()));
}
private static RequestBody createRequestBody(final TypedOutput body) {
if (body == null) {
return null;
}
final MediaType mediaType = MediaType.parse(body.mimeType());
return new RequestBody() {
@Override public MediaType contentType() {
return mediaType;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
body.writeTo(sink.outputStream());
}
@Override public long contentLength() {
return body.length();
}
};
}
private static TypedInput createResponseBody(final ResponseBody body) {
try {
if (body.contentLength() == 0) {
return null;
}
return new TypedInput() {
@Override public String mimeType() {
MediaType mediaType = body.contentType();
return mediaType == null ? null : mediaType.toString();
}
@Override public long length() {
try {
return body.contentLength();
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid length for its response");
}
@Override public InputStream in() throws IOException {
return body.byteStream();
}
};
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid content length for its response");
}
private static List<Header> createHeaders(Headers headers) {
int size = headers.size();
List<Header> headerList = new ArrayList<Header>(size);
for (int i = 0; i < size; i++) {
headerList.add(new Header(headers.name(i), headers.value(i)));
}
return headerList;
}
}
참고 URL : https://stackoverflow.com/questions/23103174/does-okhttp-support-accepting-self-signed-ssl-certs
'IT박스' 카테고리의 다른 글
null을 반환 할 수있는 모든 메서드에 대해 Java8 / Guava Optional을 사용해야합니까? (0) | 2020.12.05 |
---|---|
node.js 디버그 포트를 변경하는 방법은 무엇입니까? (0) | 2020.12.05 |
Play Framework 2.x 용 SBT 컴파일은 기본적으로 비활성화되어 있습니다. (0) | 2020.12.05 |
HTTP를 사용하여 설정된 HTTPS를 통해 쿠키 읽기 (0) | 2020.12.05 |
기존 MVC 웹 응용 프로그램에 웹 API를 추가 한 후 404 오류 (0) | 2020.12.04 |