1. 程式人生 > >Android客戶端與Java tomcat之間HTTPS通訊

Android客戶端與Java tomcat之間HTTPS通訊

文中涉及到https認證和post傳參。

       不使用SSL(Secure Sockets Layer)/TLS的HTTP通訊就是不加密的通訊,所有資訊都已明文傳播,容易被竊取、篡改或冒充。SSL/TLS協議的基本思路是採用公鑰加密法:客戶端先向伺服器端索要公鑰,然後用公鑰資訊加密,伺服器收到密文後,用自己的私鑰解密。(公鑰加解密的計算量比較大,在伺服器與客戶端通訊的握手過程中會產生一個使用對稱加密的“對話金鑰”,以便減少計算量,具體見其他資料)

系統:tomcat

開發工具:AndroidStudio

單向認證:伺服器端認證

一、生成金鑰和證書(證書也就是公鑰)

可以參考以下金鑰生成指令碼,根據實際情況做必要的修改,其中需要注意的是:伺服器的金鑰庫引數“CN”必須與伺服器的域名相同,不要填寫

IP地址,否則會出現hostname無法驗證的情況(見谷歌官方安卓開發train),客戶端則任意。圖中的"-storepass 123456 –keypass 123456"即為私鑰。

<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