Android客戶端與Java tomcat之間HTTPS通訊
文中涉及到https認證和post傳參。
不使用SSL(Secure Sockets Layer)/TLS的HTTP通訊就是不加密的通訊,所有資訊都已明文傳播,容易被竊取、篡改或冒充。SSL/TLS協議的基本思路是採用公鑰加密法:客戶端先向伺服器端索要公鑰,然後用公鑰資訊加密,伺服器收到密文後,用自己的私鑰解密。(公鑰加解密的計算量比較大,在伺服器與客戶端通訊的握手過程中會產生一個使用對稱加密的“對話金鑰”,以便減少計算量,具體見其他資料)
系統:tomcat
開發工具:AndroidStudio
單向認證:伺服器端認證
一、生成金鑰和證書(證書也就是公鑰)
可以參考以下金鑰生成指令碼,根據實際情況做必要的修改,其中需要注意的是:伺服器的金鑰庫引數“CN”必須與伺服器的域名相同,不要填寫
<span style="font-size:18px;">1、生成伺服器證書庫 keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore E:\ssl\server.keystore -dname "CN=127.0.0.1,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456 2、生成客戶端證書庫 keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore E:\ssl\client.p12 -dname "CN=client,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456 3、從客戶端證書庫中匯出客戶端證書 keytool -export -v -alias client -keystore E:\ssl\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file E:\ssl\client.cer 4、從伺服器證書庫中匯出伺服器證書 keytool -export -v -alias server -keystore E:\ssl\server.keystore -storepass 123456 -rfc -file E:\ssl\server.cer 5、生成客戶端信任證書庫(由服務端證書生成的證書庫) keytool -import -v -alias server -file E:\ssl\server.cer -keystore E:\ssl\client.truststore -storepass 123456 6、將客戶端證書匯入到伺服器證書庫(使得伺服器信任客戶端證書) keytool -import -v -alias client -file E:\ssl\client.cer -keystore E:\ssl\server.keystore -storepass 123456 7、檢視證書庫中的全部證書 keytool -list -keystore E:\ssl\server.keystore -storepass 123456</span>
會得到server.cer、client.cer(公鑰)、server.keystore(伺服器金鑰庫)等幾個檔案。server.cer會在android客戶端的時候使用,用來加密資訊。
二、Tomcat配置
在Tomcat的安裝目錄下建立資料夾key用於存放證書。
編輯${catalina.base}/conf/server.xml檔案(${catalina.base}指Tomcat的安裝目錄)。找到Connector port="8443"的標籤,取消註釋,並修改成如下:
<span style="font-size:18px;"><Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456" truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456"/></span>
備註:
keystoreFile:指定伺服器金鑰庫,可以配置成絕對路徑,如“D:/key/server.keystore”,本例中使用相對路徑,且在Tomcat目錄中建立了一個名稱為key的資料夾,僅供參考。
keystorePass:金鑰庫生成時的密碼
truststoreFile:受信任金鑰庫,和金鑰庫相同即可
truststorePass:受信任金鑰庫密碼
三、伺服器端
編寫了一個簡單的HttpServlet,如下。
<span style="font-size:18px;">public class HelloWorld extends HttpServlet{
@Override
protected void doGet(HttpServletRequest hsr, HttpServletResponse resp)
throws IOException{
PrintWriter out=resp.getWriter();
out.println("hello servlet by httpServlet!");
String password = hsr.getParameter("password");
out.println(password);
resp.setContentType(password);
}
@Override
protected void doPost(HttpServletRequest hsr, HttpServletResponse resp)
throws IOException{
doGet(hsr, resp);
}
}</span>
對web.xml檔案,如下。
<span style="font-size:18px;"><?xml version="1.0" encoding="ISO-8859-1"?>
<web-app metadata-complete="true" version="3.1"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee">
<servlet>
<servlet-name>servletLearn</servlet-name>
<servlet-class>HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletLearn</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<!-- 強制SSL配置,即普通的請求也會重定向為SSL請求 -->
<security-constraint>
<web-resource-collection>
<web-resource-name>SSL</web-resource-name>
<url-pattern>/hello</url-pattern><!-- hello使用SSL -->
</web-resource-collection>
<user-data-constraint>
<description>SSL required</description>
<!-- CONFIDENTIAL: 要保證伺服器和客戶端之間傳輸的資料不能夠被修改,且不能被第三方檢視到 -->
<!-- INTEGRAL: 要保證伺服器和client之間傳輸的資料不能夠被修改 -->
<!-- NONE: 指示容器必須能夠在任一的連線上提供資料。(即用HTTP或HTTPS,由客戶端來決定)-->
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app></span>
至此,伺服器端的配置完成,可通過瀏覽器進行測試。在區域網下,要使用域名進行測試,則需要在路由器上設定域名轉發。
四、Android客戶端
Android客戶端首先需要將server.cer(伺服器端公鑰)儲存至Android專案的assets資料夾下,如下圖。
連線伺服器並post傳值,程式碼如下,.
<span style="font-size:18px;"> private String connectHttpsPost(String param){
String result = "";
InputStream caInput = null;
try{
caInput = getAssets().open("server.cer");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try{
ca = cf.generateCertificate(caInput);
}finally {
caInput.close();
}
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca",ca);
SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore);
HttpClient httpClient = new DefaultHttpClient();
Scheme schema = new Scheme("https", socketFactory, 443);
httpClient.getConnectionManager().getSchemeRegistry().register(schema);
HttpPost httpPost = new HttpPost("https://testhttps.com:8443/testServlet/hello");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("password",param));
httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
HttpResponse httpResponse = httpClient.execute(httpPost);
if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
HttpEntity entity = httpResponse.getEntity();
if(entity != null){
result = readInputStream(entity.getContent());
}
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}</span>
程式碼中的讀取及處理server.cer的程式碼來自谷歌官方Android文件http://developer.android.com/training。
自此,Android客戶端已能通過https訪問伺服器端。使用Wireshark抓包也沒有抓取到傳遞的資訊,如下圖
總結:
這種方式似乎只要客戶端擁有伺服器的公鑰就能與伺服器通訊。有一個問題是:像新浪微博、QQ空間等等網站都能授權第三方使用其介面,沒有取得授權證書便不能。上文中實現的方法,似乎只要獲取到伺服器的公鑰便可使用伺服器的介面。
猜想:第三方授權會給開發者一個金鑰,這個金鑰可能就是上文中提到的client.cer授權的工作只是將這個client.ser匯入到伺服器證書庫中(使得伺服器信任客戶端證書)server.keystore。那麼Android客戶端端就需要讀取client.cer檔案了。
參考:
http://www.blogjava.net/icewee/archive/2012/06/04/379947.html
android httpClient 支援HTTPS的2種處理方式:
http://my.oschina.net/blackylin/blog/144136