由于公司IOS的https改版要求,所以后台跟着做成了https的网络请求,因此有了Android的https改版需求。有看了鸿洋大神的文章,和一些相关的资料,总结https的知识如下
首先我们来说说https和http的差别:
参考文章: http://blog.csdn.net/mingli198611/article/details/8055261
我们的产品经理曾经戏称https和http请求,无非就是多了一个s的区别,仅此而已嘛,其实不然。就像文章中所说,http是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET 请求获取Request-URI所标识的资源
POST 在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应消息报头
PUT 请求服务器存储一个资源,并用Request-URI作为其标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
返回的代码状态:
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
HTTPS(Hypertext Transfer Protocol over Secure Socket Layer,基于SSL的HTTP协议)使用了HTTP协议,但HTTPS使用不同于HTTP协议的默认端口及一个加密、身份验证层(HTTP与TCP之间)。
其实两者最大的不同,在于https的保密协议更严格,简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
大概了解了http和https的区别之后,我们来说一下从Android方面,如何与https协议的服务器交互,调用接口数据,一般来说,大致有三种方式来处理:
一:在客户端默认信任所有的证书,这样做是可以调用加密https的接口数据,但是相对而言,也没有安全性可言,修改为https的作用也就不存在了,本身就是为了提高用户数据的安全性
- public class HttpsTestActivity extends Activity {
- /** Called when the activity is first created. */
- private TextView text;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- text=(TextView)findViewById(R.id.textView1);
- GetHttps();
- }
- private void GetHttps(){
- String https = "https://www.google.com.hk";
- try{
- SSLContext sc = SSLContext.getInstance("TLS");
- sc.init(null, new TrustManager[]{new MyTrustManager()}, new SecureRandom());
- HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
- HttpsURLConnection.setDefaultHostnameVerifier(new MyHostnameVerifier());
- HttpsURLConnection conn = (HttpsURLConnection)new URL(https).openConnection();
- conn.setDoOutput(true);
- conn.setDoInput(true);
- conn.connect();
- BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- StringBuffer sb = new StringBuffer();
- String line;
- while ((line = br.readLine()) != null)
- sb.append(line);
- text.setText(sb.toString());
- }catch(Exception e){
- Log.e(this.getClass().getName(), e.getMessage());
- }
- }
- 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;
- }
- }
- }
二:在客户端加入自己的证书,并且在访问时候提交服务器验证证书信息,调取接口,大致过程:
1、在自己的Assets文件下,加入证书的文件,一般后缀名为.CER的文件(证书文件)
2、在初始化的BaseApplication中,调用验证证书的方法
3、添加工具类,其中封装了关于https的请求的方法,关于httpsUtils的类,参考资料 http://www.jianshu.com/p/2f6ace079568
4、测试修改
public class HttpsUtil {
private static final String[] CERTIFICATES = new String[] { "xxx.cer" };
/**
* HttpUrlConnection支持Https验证(单向,足够满足大多数业务的需求)
* <p>
* 对安全有更高要求的业务如银行、金融等,需要双向验证,可自定义
*
* @param context
*/
public static void initHttpsUrlConnection(Context context) {
InputStream[] certificates = getCertificates(context, CERTIFICATES);
SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certificates,
null, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
if (certificates == null) {
HttpsURLConnection
.setDefaultHostnameVerifier(getUnSafeHostnameVerifier());
}
}
/**
* 获取支持Https的OkHttpClient
* <p>
* 不需要的项目可注释
*
* @param context
* @return
*/
@SuppressWarnings("deprecation")
public static OkHttpClient getHttpsOkHttpClient(Context context) {
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
InputStream[] certificates = HttpsUtil.getCertificates(context,
CERTIFICATES);
SSLSocketFactory sslSocketFactory = HttpsUtil.getSSLSocketFactory(
certificates, null, null);
builder.sslSocketFactory(sslSocketFactory);
if (certificates == null) {
builder.hostnameVerifier(HttpsUtil.getUnSafeHostnameVerifier());
}
return builder.build();
}
/**
* 获取服务端证书
* <p>
* 默认放在Assets目录下
*
* @param context
* @return
*/
public static InputStream[] getCertificates(Context context,
String... fileNames) {
if (context == null || fileNames == null || fileNames.length <= 0) {
return null;
}
try {
InputStream[] certificates = new InputStream[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
certificates[i] = context.getAssets().open(fileNames[i]);
}
return certificates;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取自定义SSLSocketFactory
* <p>
* 单项验证时只需要certificates,其余null即可 双向验证时,3个参数均需要
* <p>
* 不验证,即信任所有证书时全部传null,同时配合getUnSafeHostnameVerifier() 有安全隐患,慎用!!!
*
* @param certificates
* 服务端证书(.crt)
* @param bksFile
* 客户端证书请求文件(.jsk -> .bks)
* @param password
* 生成jks时的密钥库口令
* @return
*/
public static SSLSocketFactory getSSLSocketFactory(
InputStream[] certificates, InputStream bksFile, String password) {
try {
TrustManager[] trustManagers = prepareTrustManager(certificates);
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
if (trustManagers == null || trustManagers.length <= 0) {
trustManagers = new TrustManager[] { new UnSafeTrustManager() };
}
sslContext.init(keyManagers, trustManagers, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AssertionError(e);
}
}
private static TrustManager[] prepareTrustManager(
InputStream... certificates) {
if (certificates == null || certificates.length <= 0)
return null;
try {
CertificateFactory certificateFactory = CertificateFactory
.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream is : certificates) {
String certificateAlias = Integer.toString(index++);
Certificate certificate = certificateFactory
.generateCertificate(is);
keyStore.setCertificateEntry(certificateAlias, certificate);
try {
if (is != null)
is.close();
} catch (IOException ignored) {
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
return trustManagerFactory.getTrustManagers();
// TODO: 2016/11/11 针对有效期异常导致校验失败的情况,目前没有完美的解决方案
// TrustManager[] keyStoreTrustManagers =
// trustManagerFactory.getTrustManagers();
// return getNotValidateTimeTrustManagers((X509TrustManager[])
// keyStoreTrustManagers);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static KeyManager[] prepareKeyManager(InputStream bksFile,
String password) {
try {
if (bksFile == null || password == null)
return null;
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(bksFile, password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, password.toCharArray());
return keyManagerFactory.getKeyManagers();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static NotValidateTimeTrustManager[] getNotValidateTimeTrustManagers(
X509TrustManager[] trustManagers) {
NotValidateTimeTrustManager[] notValidateTimeTrustManagers = new NotValidateTimeTrustManager[trustManagers.length];
for (int i = 0; i < trustManagers.length; i++) {
notValidateTimeTrustManagers[i] = new NotValidateTimeTrustManager(
trustManagers[i]);
}
return notValidateTimeTrustManagers;
}
/**
* 不校验证书有效期的TrustManager
* <p>
* 防止用户乱改手机时间导致校验失败 注意:由于校验证书时,对有效期的校验并不是最后一项,所以该TrustManager仍然存在安全隐患,并不推荐使用
*/
private static class NotValidateTimeTrustManager implements
X509TrustManager {
private X509TrustManager defaultTrustManager;
public NotValidateTimeTrustManager(X509TrustManager defaultTrustManager) {
this.defaultTrustManager = defaultTrustManager;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
e.printStackTrace();
Throwable t = e;
while (t != null) {
if (t instanceof CertificateExpiredException
|| t instanceof CertificateNotYetValidException)
return;
t = t.getCause();
}
throw e;
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
}
private static class UnSafeTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
}
private static class UnSafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
/**
* 不验证,即信任所有证书时使用 有安全隐患,慎用!!!
*
* @return
*/
public static UnSafeHostnameVerifier getUnSafeHostnameVerifier() {
return new UnSafeHostnameVerifier();
}
}
(这里注意两个方法,适用于其他http网络jar包的验证方法initHttpsUrlConnection,适用于okhttp的验证方法getHttpsOkHttpClient)
三:关于https的双向认证,这样的情况,一般用于证书文件是自签名的文件,而且安全性相对要求较高的程序应用,需要适用双向认证,说白了,我的理解就是两个证书,一个客户端一个服务端,交互的时候互相认证,大致过程:
- 构造CertificateFactory对象,通过它的
generateCertificate(is)
方法得到Certificate。 - 然后讲得到的
Certificate
放入到keyStore中。 - 接下来利用keyStore去初始化我们的
TrustManagerFactory
- 由
trustManagerFactory.getTrustManagers
获得TrustManager[]
初始化我们的SSLContext
- 最后,设置我们mOkHttpClient.setSslSocketFactory即可。
另外几个相关的类的下载地址等待审核通过后更新在文章中,其中的工具lib类库是根据鸿杨项目修改的eclipse的lib类库,以及其中没有包含的在大神另外的一片文章中的OkHttpClientManager类,仅仅作为一个收集总结的文章,转载的相关资料已经标明