I am writing an Android application which has to connect to a server through HTTPS. The first solution I tried was this one:
(Don’t mind the security flaws)
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
private static void trustAllHosts() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
} };
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
Log.d("USR_SSL", e.getMessage());
}
}
//...
@Override
protected String doInBackground(String... params) {
try {
URL url = new URL(params[0]);
//This is an HTTPS url
String jsonStr = "";
if(params.length > 1) {
jsonStr = params[1];
}
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
trustAllHosts();
urlConn.setHostnameVerifier(DO_NOT_VERIFY);
urlConn.setRequestProperty("Content-Type", "application/json");
urlConn.setRequestProperty("Accept", "application/json");
urlConn.setRequestMethod("POST");
OutputStream os = urlConn.getOutputStream();
os.write(jsonStr.getBytes());
os.flush();
//...
All fine and dandy (almost), until I realised that I also have to use authentication, session, and all that good stuff. It should have been really fine using:
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
but unfortunately we have to support Android API level 8, which means the above two lines of code will not work. Given that, I’ve scoured the Internet for a few hours trying to build a solution using Apache classes, which seemingly support both HTTPS and Cookies.
This is the code I’ve managed to sew together:
public class ConnectionMediator {
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
public void tryConnect(String url, String data) {
try {
//SSL Stuff
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
DefaultHttpClient httpClient = new DefaultHttpClient(ccm, params);
//Cookie stuff
HttpContext localContext = new BasicHttpContext();
HttpResponse response = null;
HttpPost httpPost = null;
StringEntity tmp = null;
httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
httpPost = new HttpPost(url);
tmp = new StringEntity(data,"UTF-8");
httpPost.setEntity(tmp);
response = httpClient.execute(httpPost,localContext);
} catch(Exception e) {
Log.d("USR_DEBUG", e.getClass().toString() + ": " + e.getMessage());
}
}
}
At the time of writing this, I get a NetworkOnMainThreadException, but this is rather unimportant; what matters and what I wish to point out is that i have no idea what I’m doing, as in, to simply connect through by means of HTTPS and also use cookies, one has to use 13 different classes which I’ve never heard of. Obviously, my knowledge of HTTPS/Java net classes is bordering null, but in spite of this I would have expected something more intuitive. So rather than a “this doesn’t work” type of question, my question is “what should I be doing”, or even, “how do I learn what I have to do?”.
Thank you very much,
a very confused coder
My initial question had two parts to it: first, how to use HTTPS and second, how to also use cookies along with it.
My question wasn’t thorough enough, as I had partly answered it already – the code which I had initially posted worked with respect to HTTPS, and the
NetworkOnMainThreadExceptionoccurred because I was not running the code in a separate thread, for example, usingAsyncTask.However, to also make proper use of cookies, one should make use of a solution similar to the following:
I am not sure if this is the ‘Android’ way of doing it (using a static class) but it works:
It seems using another
HttpClientevery time preserves the session, provided that the sameCookieStoreandHttpContextare used.While this answer makes ‘things work’ (which is what I needed right now), it is not a thorough answer as it does not explain at all why more than 10 classes are needed to connect everything together.