由于项目要求为了保证服务器数据安全,保证接口不暴露给第三方,要求我们客户端接口全部采用HTTPS的SSL验证请求,花费一个多星期才搞定IOS和Android端和Tomcat服务器间的HTTPS的SSL验证请求,其中不免遇到了许多问题和麻烦,所以写下这篇文章希望对有需要的有所帮助。
下载地址http://download.csdn.net/detail/wangyadong99/9180201
一、HTTPS和HTTP的区别
1、https协议需要到ca申请证书,一般免费证书很少,需要交费。
2、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
二、SSL功能
1)客户对服务器的身份认证:
SSL服务器允许客户的浏览器使用标准的公钥加密技术和一些可靠的认证中心(CA)的证书,来确认服务器的合法性。
2)服务器对客户的身份认证:
也可通过公钥技术和证书进行认证,也可通过用户名,password来认证。
3)建立服务器与客户之间安全的数据通道:
SSL要求客户与服务器之间的所有发送的数据都被发送端加密、接收端解密,同时还检查数据的完整性。
SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:
SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
三、生成密钥库和证书
首先打开终端 cd到生成的证书路径下我的在/Users/monkey/Desktop/AA/DD/
1、生成服务器证书库
localhost:DD monkey$ keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=192.168.1.110,OU=monkey,O=monkey,L=BeiJIng,ST=BeiJing,c=cn" -storepass 123456 -keypass 123456
2、生成客户端证书库
localhost:DD monkey$ keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=client,OU=monkey,O=monkey,L=BeiJing,ST=BeiJing,c=cn" -storepass 123456 -keypass 123456
3、从客户端证书库中导出客户端证书
localhost:DD monkey$ keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass 123456 -rfc -file client.cer
(Download bouncycastle JAR fromhttp://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jar复制 bcprov-ext-jdk15on-1.46.jar 到 /Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/bcprov-ext-jdk15on-1.46.jar中)
4、从服务器证书库中导出服务器证书
localhost:DD monkey$ keytool -export -v -alias server -keystore server.keystore -storepass 123456 -rfc -file server.cer
5、生成Android客户端信任证书库(由服务端证书生成的证书库)
localhost:DD monkey$ keytool -importcert -keystore server.bks -file server.cer -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)
localhost:DD monkey$ keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
7、查看证书库中的全部证书
keytool -list -keystore server.keystore -storepass 123456
通过上面的步骤生成的证书,客户端需要用到的是client.p12(客户端证书,用于请求的时候给服务器来验证身份之用)和client.truststore(客户端证书库,用于验证服务器端身份,防止钓鱼)这两个文件.
四、Tomcat配置
在server.xml中把https的注释取消
<ConnectorSSLEnabled="true"acceptCount="100"clientAuth="true"debug="0" disableUploadTimeout="true"enableLookups="true"keystoreFile="/Users/monkey/Desktop/AA/DD/server.keystore"keystorePass="123456"truststoreFile="/Users/monkey/Desktop/AA/DD/server.keystore"truststorePass="123456"maxProcessors="75"maxThreads="150"minProcessors="5"port="8443"protocol="org.apache.coyote.http11.Http11Protocol"scheme="https"secure="true"sslProtocol="TLS"/>
配置成功后就可以在浏览器中测试访问了
双击client.p12导入到浏览器
输入程序网址https://192.168.1.110:8443/AijiaWebSvc/
五、IOS相关代码
在调用网络请求时添加如下代码,其他代码照常
-(BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
returnYES;
}
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
NSLog(@"authenticatemethod:%@",protectionSpace.authenticationMethod);
returnYES;
}
//回调用时会执行两边
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(@"Authentication challenge");
if ([challenge.protectionSpace.authenticationMethodisEqualToString:NSURLAuthenticationMethodServerTrust])
{
//服务器证书认证
[challenge.senderuseCredential:[NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust]
forAuthenticationChallenge:challenge];
}
elseif ([challenge.protectionSpace.authenticationMethodisEqualToString:NSURLAuthenticationMethodClientCertificate])
{
//客户端证书认证
//TODO:设置客户端证书认证
// load cert
NSString *path = [[NSBundlemainBundle]pathForResource:@"client"ofType:@"p12"];
NSData *p12data = [NSDatadataWithContentsOfFile:path];
CFDataRef inP12data = (__bridgeCFDataRef)p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
constvoid *certs[] = { myCertificate };
CFArrayRef certsArray =CFArrayCreate(NULL, certs,1,NULL);
NSURLCredential *credential = [NSURLCredentialcredentialWithIdentity:myIdentitycertificates:(__bridgeNSArray*)certsArraypersistence:NSURLCredentialPersistencePermanent];
[[challenge sender]useCredential:credentialforAuthenticationChallenge:challenge];
}
return;
}
六、Android相关代码
把client.p12和server.bks拷贝到项目assets中
public class SSLConnection
{
private static TrustManager[]trustManagers;
private staticfinal StringKEY_STORE_TYPE_BKS ="bks";
private staticfinal StringKEY_STORE_TYPE_P12 ="PKCS12";
private staticfinal StringkeyStoreFileName ="client.p12";
private staticfinal StringkeyStorePassword ="123456";
private staticfinal StringtrustStoreFileName ="server.bks";
private staticfinal StringtrustStorePassword ="123456";
private staticfinal Stringalias =null;//"client";
private static ContextpContext =null;
public staticvoid allowAllSSL(Contextct)
{
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
publicboolean verify(Stringhostname, SSLSessionsession) {
//TODO Auto-generated method stub
returntrue;
}
});
pContext=ct;
javax.net.ssl.SSLContext context;
if(trustManagers==null)
{
try
{
KeyManager[] keyManagers = createKeyManagers(keyStoreFileName,keyStorePassword,alias);
trustManagers=createTrustManagers(trustStoreFileName,trustStorePassword);
context=javax.net.ssl.SSLContext.getInstance("TLS");
context.init(keyManagers,trustManagers,new SecureRandom());
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
} catch (KeyStoreExceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmExceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateExceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
} catch (IOExceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
}catch (UnrecoverableKeyExceptione) {
//TODO Auto-generated catch block
e.printStackTrace();
}
catch (KeyManagementExceptione)
{
Log.e("allowAllSSL",e.toString());
}//new TrustManager[]{new _FakeX509TrustManager()};
}
}
private static KeyManager[] createKeyManagers(StringkeyStoreFileName, StringkeyStorePassword, Stringalias)
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
{
InputStream inputStream =pContext.getResources().getAssets().open(keyStoreFileName);
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
keyStore.load(inputStream,keyStorePassword.toCharArray());
printKeystoreInfo(keyStore);//for debug
KeyManager[] managers;
if (alias !=null)
{
managers =
new KeyManager[] {
new SSLConnection().new AliasKeyManager(keyStore,alias,keyStorePassword)};
}
else
{
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());//PKIX "X509")
keyManagerFactory.init(keyStore,keyStorePassword ==null ?null :keyStorePassword.toCharArray());
managers =keyManagerFactory.getKeyManagers();
}
returnmanagers;
}
private static TrustManager[] createTrustManagers(StringtrustStoreFileName, StringtrustStorePassword)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
InputStream inputStream =pContext.getResources().getAssets().open(trustStoreFileName);
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
trustStore.load(inputStream,trustStorePassword.toCharArray());
printKeystoreInfo(trustStore);//for debug
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
return trustManagerFactory.getTrustManagers();
}
private staticvoid printKeystoreInfo(KeyStorekeystore)throws KeyStoreException {
System.out.println("Provider : " +keystore.getProvider().getName());
System.out.println("Type : " +keystore.getType());
System.out.println("Size : " +keystore.size());
Enumeration en = keystore.aliases();
while (en.hasMoreElements()) {
System.out.println("Alias: " +en.nextElement());
}
}
private class AliasKeyManagerimplements X509KeyManager
{
private KeyStore_ks;
private String _alias;
private String _password;
public AliasKeyManager(KeyStoreks, Stringalias, String password) {
_ks =ks;
_alias =alias;
_password =password;
}
public String chooseClientAlias(String[]str, Principal[]principal, Socketsocket) {
return_alias;
}
public String chooseServerAlias(Stringstr, Principal[]principal, Socketsocket) {
return_alias;
}
public X509Certificate[] getCertificateChain(Stringalias) {
try
{
java.security.cert.Certificate[]certificates =this._ks.getCertificateChain(alias);
if(certificates ==null)
{
thrownew FileNotFoundException("no certificate found for alias:" +alias);
}
X509Certificate[]x509Certificates =new X509Certificate[certificates.length];
System.arraycopy(certificates, 0,x509Certificates, 0,certificates.length);
returnx509Certificates;
} catch (Exceptione) {
e.printStackTrace();
returnnull;
}
}
public String[] getClientAliases(Stringstr, Principal[]principal)
{
returnnew String[] {_alias };
}
public PrivateKey getPrivateKey(Stringalias)
{
try
{
return (PrivateKey)_ks.getKey(alias,_password == null ? null :_password.toCharArray());
} catch (Exceptione)
{
e.printStackTrace();
returnnull;
}
}
public String[] getServerAliases(Stringstr, Principal[]principal)
{
returnnew String[] {_alias };
}
}
}
调用网络请求前只需加SSLConnection.allowAllSSL(MainActivity.this);就可以正常访问了
参考https://developer.android.com/training/articles/security-ssl.html#HttpsExample
http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Certificates