android中对部分站点发送https请求会报错,原因是该站点的证书时自定义的,而非官方的,android手机不信任其证书,为了解决这个问题,一般有两种解决方案
-
忽略证书验证
-
下载证书到本地,添加到信任证书列表
在android中我们访问网络一般有两种方式,一种是使用httpURLConnection,一种是使用httpClient。这里先讲前者,然后再讲解后者。顺便会讲解在volley框架中如何使用https。
一,HttpURLConnection访问https站点
1,忽略证书
如果要忽略证书的验证,我们只需提供一个HostnameVerifier和X509TrustManager的空实现,如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public void requestWithoutCA() {
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new MyTrustManager() },
new SecureRandom());
HttpsURLConnection
.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection
.setDefaultHostnameVerifier(new MyHostnameVerifier());
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpURLConnection urlConnection = (HttpURLConnection) url
.openConnection();
InputStream in = urlConnection.getInputStream();
// 取得输入流,并使用Reader读取
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
System.out.println("=============================");
System.out.println("Contents of get request");
System.out.println("=============================");
String lines;
while ((lines = reader.readLine()) != null) {
System.out.println(lines);
}
reader.close();
// 断开连接
urlConnection.disconnect();
System.out.println("=============================");
System.out.println("Contents of get request ends");
System.out.println("=============================");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private class MyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
// TODO Auto-generated method stub
return true;
}
}
private class MyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}
2,使用证书
使用证书这部分我是参考于官网: Security with HTTPS and SSL
该代码时访问华盛顿大学的一个网页,证书可以到以下链接下载:
https://www.washington.edu/itconnect/security/ca/load-der.crt
代码如下:
注意,此处使用的是HttpsURLConnection,而非HttpURLConnection。
下面的代码,加载证书等等操作,最终都是为了setSSLSocketFactory这一步。
public void requestByHttps() {
try {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From
// https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = this.getAssets().open("load_der.crt");
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca="
+ ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection = (HttpsURLConnection) url
.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
// 取得输入流,并使用Reader读取
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
System.out.println("=============================");
System.out.println("Contents of get request");
System.out.println("=============================");
String lines;
while ((lines = reader.readLine()) != null) {
System.out.println(lines);
// tv.setText(tv.getText().toString() + lines);
}
reader.close();
// 断开连接
urlConnection.disconnect();
System.out.println("=============================");
System.out.println("Contents of get request ends");
System.out.println("=============================");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3, Volley中使用HttpURLConnection访问https
Volley中使用HttpURLConnection访问网络时,是由HurlStack类处理,该类的几个构造方法如下:
public HurlStack() {
this(null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
*/
public HurlStack(UrlRewriter urlRewriter) {
this(urlRewriter, null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
* @param sslSocketFactory SSL factory to use for HTTPS connections
*/
public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
mUrlRewriter = urlRewriter;
mSslSocketFactory = sslSocketFactory;
}
打开连接的方法如下,注意其中也有上面提到的的setSSLSocketFactory方法。
结合构造方法以及以下的代码,不难理解,为了访问https,只需奥在构建HurlStack时,传入一个已经设置了证书的SLSocketFactory即可,设置代码和上面的代码类似。
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = createConnection(url);
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
// use caller-provided custom SslSocketFactory, if any, for HTTPS
if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
}
return connection;
}
二,使用httpClient访问https站点
1,忽略证书
public class HttpClientHelper {
private static HttpClient httpClient;
private HttpClientHelper() {
}
public static synchronized HttpClient getHttpClient() {
if (null == httpClient) {
// 初始化工作
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //允许所有主机的验证
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params,
HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(params, true);
// 设置连接管理器的超时
ConnManagerParams.setTimeout(params, 10000);
// 设置连接超时
HttpConnectionParams.setConnectionTimeout(params, 10000);
// 设置socket超时
HttpConnectionParams.setSoTimeout(params, 10000);
// 设置http https支持
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("http", PlainSocketFactory
.getSocketFactory(), 80));
schReg.register(new Scheme("https", sf, 443));
ClientConnectionManager conManager = new ThreadSafeClientConnManager(
params, schReg);
httpClient = new DefaultHttpClient(conManager, params);
} catch (Exception e) {
e.printStackTrace();
return new DefaultHttpClient();
}
}
return httpClient;
}
}
class SSLSocketFactoryEx extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactoryEx(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
}
};
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();
}
}
2,使用证书
AssetManager am = context.getAssets();
InputStream ins = am.open("robusoft.cer");
try {
//读取证书
CertificateFactory cerFactory = CertificateFactory.getInstance("X.509"); //问1
Certificate cer = cerFactory.generateCertificate(ins);
//创建一个证书库,并将证书导入证书库
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); //问2
keyStore.load(null, null);
keyStore.setCertificateEntry("trust", cer);
return keyStore;
} finally {
ins.close();
}
//把咱的证书库作为信任证书库
SSLSocketFactory socketFactory = new SSLSocketFactory(keystore);
Scheme sch = new Scheme("https", socketFactory, 443);
//完工
HttpClient mHttpClient = new DefaultHttpClient();
mHttpClient.getConnectionManager().getSchemeRegistry().register(sch);
//接下来这个httpclient就可以用语https请求了。
3,Volley中使用httpClient访问https
Volley使用HttpCLient访问网络是借助HttpClientStack类,其中的构造方法不同于HurlStack那么多,而只有一个
public HttpClientStack(HttpClient client) {
mClient = client;
}
很简单,我们只需要在构造HttpClientStack时传入一个已经设置了证书的HttpClient,按照上面讲述的那样设置即可。