首先12306網站前綴為“https://” 表明是用SSL加密。
用HttpClient去模擬發送請求時,對於URL用為“https”時,先要解決證書問題,有兩種解決方案:
a.使證書被信任。
在查找相關資料時,對於這種方法有點麻煩,最后就沒有去嘗試,有興趣的朋友可以試試。
b.使用httpClient時不檢測服務器證書是否可信
擴展HttpClient 類實現自動接受證書,因為這種方法自動接收所有證書,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統的安全需求。
具體的步驟如下:
•提供一個自定義的socket factory (test.MySecureProtocolSocketFactory )。這個自定義的類必須實現接口
org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory ,在實現接口的類中調用自定義的
X509TrustManager(test.MyX509TrustManager)
•創建一個org.apache.commons.httpclient.protocol.Protocol 的實例,指定協議名稱和默認的端口號
Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);
•注冊剛才創建的https 協議對象
Protocol.registerProtocol("https ", myhttps);
具體代碼如下:
1 package org.study.meteor.ticket.util; 2 3 import java.io.IOException; 4 import java.net.InetAddress; 5 import java.net.InetSocketAddress; 6 import java.net.Socket; 7 import java.net.SocketAddress; 8 import java.net.UnknownHostException; 9 import java.security.KeyManagementException; 10 import java.security.NoSuchAlgorithmException; 11 import java.security.cert.CertificateException; 12 import java.security.cert.X509Certificate; 13 14 import javax.net.SocketFactory; 15 import javax.net.ssl.SSLContext; 16 import javax.net.ssl.TrustManager; 17 import javax.net.ssl.X509TrustManager; 18 19 import org.apache.commons.httpclient.ConnectTimeoutException; 20 import org.apache.commons.httpclient.params.HttpConnectionParams; 21 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; 22 23 /** 24 * MySecureProtocolSocketFactory.java.java Create on 2012-9-26下午1:15:03 25 * 26 * 27 * Copyright (c) 2012 by MTA. 28 * 29 * @author lmeteor 30 * @Email txin0814@sina.com 31 * @description 自定義的socket factory 實現自動接受證書 32 * @version 1.0 33 */ 34 public class MySecureProtocolSocketFactory implements 35 SecureProtocolSocketFactory 36 { 37 38 private SSLContext sslcontext = null; 39 40 private SSLContext createSSLContext() 41 { 42 SSLContext sslcontext = null; 43 try 44 { 45 sslcontext = SSLContext.getInstance("SSL"); 46 sslcontext.init(null, new TrustManager[] 47 { new TrustAnyTrustManager() }, new java.security.SecureRandom()); 48 } 49 catch (NoSuchAlgorithmException e) 50 { 51 e.printStackTrace(); 52 } 53 catch (KeyManagementException e) 54 { 55 e.printStackTrace(); 56 } 57 return sslcontext; 58 } 59 60 private SSLContext getSSLContext() 61 { 62 if (this.sslcontext == null) 63 { 64 this.sslcontext = createSSLContext(); 65 } 66 return this.sslcontext; 67 } 68 69 public Socket createSocket(Socket socket, String host, int port, 70 boolean autoClose) throws IOException, UnknownHostException 71 { 72 return getSSLContext().getSocketFactory().createSocket(socket, host, 73 port, autoClose); 74 } 75 76 public Socket createSocket(String host, int port) throws IOException, 77 UnknownHostException 78 { 79 return getSSLContext().getSocketFactory().createSocket(host, port); 80 } 81 82 public Socket createSocket(String host, int port, InetAddress clientHost, 83 int clientPort) throws IOException, UnknownHostException 84 { 85 return getSSLContext().getSocketFactory().createSocket(host, port, 86 clientHost, clientPort); 87 } 88 89 public Socket createSocket(String host, int port, InetAddress localAddress, 90 int localPort, HttpConnectionParams params) throws IOException, 91 UnknownHostException, ConnectTimeoutException 92 { 93 if (params == null) 94 { 95 throw new IllegalArgumentException("Parameters may not be null"); 96 } 97 int timeout = params.getConnectionTimeout(); 98 SocketFactory socketfactory = getSSLContext().getSocketFactory(); 99 if (timeout == 0) 100 { 101 return socketfactory.createSocket(host, port, localAddress, 102 localPort); 103 } 104 else 105 { 106 Socket socket = socketfactory.createSocket(); 107 SocketAddress localaddr = new InetSocketAddress(localAddress, 108 localPort); 109 SocketAddress remoteaddr = new InetSocketAddress(host, port); 110 socket.bind(localaddr); 111 socket.connect(remoteaddr, timeout); 112 return socket; 113 } 114 } 115 116 // 自定義私有類 117 private static class TrustAnyTrustManager implements X509TrustManager 118 { 119 120 public void checkClientTrusted(X509Certificate[] chain, String authType) 121 throws CertificateException 122 { 123 } 124 125 public void checkServerTrusted(X509Certificate[] chain, String authType) 126 throws CertificateException 127 { 128 } 129 130 public X509Certificate[] getAcceptedIssuers() 131 { 132 return new X509Certificate[] 133 {}; 134 } 135 } 136 }
下面的是httpClient的具體實現類:
1 package org.study.meteor.ticket.util; 2 3 import java.io.BufferedInputStream; 4 import java.io.File; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.io.UnsupportedEncodingException; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 import org.apache.commons.httpclient.HttpClient; 13 import org.apache.commons.httpclient.HttpException; 14 import org.apache.commons.httpclient.NameValuePair; 15 import org.apache.commons.httpclient.methods.PostMethod; 16 import org.apache.commons.httpclient.params.HttpMethodParams; 17 import org.apache.commons.httpclient.protocol.Protocol; 18 19 /** 20 * HttpDoPostUtils.java Create on 2012-9-7下午3:08:18 21 * 22 * 23 * Copyright (c) 2012 by MTA. 24 * 25 * @author lmeteor 26 * @Email txin0814@sina.com 27 * @description 模擬HTTP發送請求得到報文 28 * @version 1.0 29 */ 30 @SuppressWarnings("deprecation") 31 public class HttpDoPostUtils 32 { 33 34 35 private static HttpClient httpClient = null; 36 37 static 38 { 39 //指定協議名稱和默認的端口號 40 Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory(), 443); 41 //注冊剛才創建的https 協議對象 42 Protocol.registerProtocol("https", myhttps); 43 httpClient = new HttpClient(); 44 } 45 46 /** 47 * 發送請求報文,得到響應報文 48 * @param url 49 * 登錄請求URL 50 * @param pList 51 * 是否包含請求參數 52 * @return 53 * @throws UnsupportedEncodingException 54 */ 55 public static String doRequestToString(String url,List<NameValuePair> pList) throws UnsupportedEncodingException 56 { 57 //獲得postMethod對象 58 PostMethod pmethod = getPostMethod(url); 59 pmethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8"); 60 //判斷是否包含參數 61 if(null != pList && pList.size() > 0) 62 { 63 pmethod.setRequestBody(pList.toArray(new NameValuePair[pList.size()])); 64 } 65 String value = ""; 66 try 67 { 68 httpClient.executeMethod(pmethod); 69 value = pmethod.getResponseBodyAsString(); 70 } 71 catch ( HttpException e ) 72 { 73 e.printStackTrace(); 74 } 75 catch ( IOException e ) 76 { 77 e.printStackTrace(); 78 } 79 80 return value; 81 } 82 83 /** 84 * 獲得12306網站的登錄驗證碼 85 * @param url 86 * 請求URL 87 * @param filePath 88 * 驗證碼保存路徑 如:e:\\login.jpg 89 * @return 90 */ 91 public static File doGetFile(String url,String filePath) 92 { 93 PostMethod pmethod = getPostMethod(url); 94 pmethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8"); 95 try 96 { 97 httpClient.executeMethod(pmethod); 98 //得到響應中的流對象 99 InputStream in = pmethod.getResponseBodyAsStream(); 100 //包裝 並讀出流信息 101 BufferedInputStream bis = new BufferedInputStream(in); 102 File file = new File(filePath); 103 FileOutputStream fs = new FileOutputStream(file); 104 105 byte[] buf = new byte[1024]; 106 int len = bis.read(buf); 107 if(len == -1 || len == 0){ 108 file.delete(); 109 file = null; 110 } 111 while (len != -1) { 112 fs.write(buf, 0, len); 113 len = bis.read(buf); 114 } 115 fs.flush(); 116 fs.close(); 117 return file; 118 } 119 catch (HttpException e) 120 { 121 e.printStackTrace(); 122 } 123 catch (IOException e) 124 { 125 e.printStackTrace(); 126 } 127 return null; 128 129 } 130 131 public static List<NameValuePair> createNameValuePair(String params) { 132 List<NameValuePair> nvps = new ArrayList<NameValuePair>(); 133 if (null != params && !params.trim().equals("")) { 134 String[] _params = params.split("&"); 135 // userCookieList = new AttributeList(); 136 for (int i = 0; i < _params.length; i++) { 137 int _i = _params[i].indexOf("="); 138 if (_i != -1) { 139 String name = _params[i].substring(0, _i); 140 String value = _params[i].substring(_i + 1); 141 nvps.add(new NameValuePair(name, value)); 142 } 143 } 144 } 145 return nvps; 146 } 147 148 149 public static PostMethod getPostMethod(String url) 150 { 151 PostMethod pmethod = new PostMethod(url); 152 //設置響應頭信息 153 pmethod.addRequestHeader("Connection", "keep-alive"); 154 pmethod.addRequestHeader("Cache-Control", "max-age=0"); 155 pmethod.addRequestHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); 156 pmethod.addRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); 157 return pmethod; 158 } 159 160 }
模擬請求的類已經出來,現在開始進行模擬登錄,登錄前必須知道12306自身是怎樣提交請求,並包含哪些參數到后台,通過firebug就很容易找到這些東西了。
12306登錄之前會調用“https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest”,后台返回一串JSON報文,如下:
1 {"loginRand":"652","randError":"Y"}
當randError為“Y”時,才對表單FORM提交,並且將loginRand的值初始化到表單里的隱藏域中,作為參數傳到后台
現在最后一步就是拼接參數了,具體操作代碼如下:
1 package org.study.meteor.ticket.util; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 import java.io.UnsupportedEncodingException; 6 7 import org.study.meteor.ticket.domain.LoginBeforeValidatior; 8 9 import net.sf.json.JSONObject; 10 11 /** 12 * Login.java.java Create on 2012-9-26下午1:48:42 13 * 14 * 15 * Copyright (c) 2012 by MTA. 16 * 17 * @author lmeteor 18 * @Email txin0814@sina.com 19 * @description 20 * @version 1.0 21 */ 22 public class Login 23 { 24 25 /** 26 * 獲取驗證碼 27 * @param filePath 28 * @return 29 */ 30 public static String getRandCode(String filePath) 31 { 32 String randCode = ""; 33 /** 獲取驗證碼 */ 34 HttpDoPostUtils.doGetFile(PropertiesUtils.newInstance().getPropertiesValue("loginCode"),filePath); 35 randCode = readString("請輸入登錄驗證碼:"); 36 return randCode; 37 } 38 39 40 /** 41 * 實現登錄操作 42 * @throws UnsupportedEncodingException 43 */ 44 public static void doLogin() throws UnsupportedEncodingException 45 { 46 String randCode = getRandCode("e:\\login.jpg"); 47 /** 登錄前 提交得到報文 */ 48 String loginBeforeVal = HttpDoPostUtils.doRequestToString(PropertiesUtils.newInstance().getPropertiesValue("loginBeforeValidatiorUrl"),null); 49 //將返回的JSON報文轉換成指定的對象 50 JSONObject jsonObj = JSONObject.fromObject(loginBeforeVal); 51 LoginBeforeValidatior loginBefore = new LoginBeforeValidatior(); 52 loginBefore = (LoginBeforeValidatior) JSONObject.toBean(jsonObj, LoginBeforeValidatior.class); 53 //拼接參數 54 StringBuffer params = new StringBuffer(); 55 params.append("loginRand="+loginBefore.getLoginRand()).append("&") 56 .append("refundLogin=N").append("&") 57 .append("refundFlag=Y").append("&") 58 .append("loginUser.user_name="+PropertiesUtils.newInstance().getPropertiesValue("username")).append("&") 59 .append("nameErrorFocus=&") 60 .append("user.password="+PropertiesUtils.newInstance().getPropertiesValue("password")).append("&") 61 .append("passwordErrorFocus=&") 62 .append("randCode="+randCode).append("&") 63 .append("randErrorFocus="); 64 //像服務器發送登錄請求 並返回對應的報文 65 String loginResponseText = HttpDoPostUtils.doRequestToString(PropertiesUtils.newInstance().getPropertiesValue("loginUrl"),HttpDoPostUtils.createNameValuePair(params.toString())); 66 System.out.println(loginResponseText); 67 68 } 69 70 71 /** 72 * 多控制台讀取驗證碼 73 * @param msg 74 * @return 75 * @throws Exception 76 */ 77 private static String readString(String msg) 78 { 79 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); 80 try{ 81 System.out.print(msg+": "); 82 return bufferedReader.readLine(); 83 }catch(Exception e){ 84 } 85 return "1245"; 86 } 87 88 public static void main(String[] args) throws UnsupportedEncodingException 89 { 90 //Login login = new Login(); 91 //login.doLogin(); 92 Login.doLogin(); 93 } 94 }
URL都是在配置文件中的,大致如下:
1 #12306登錄之前調用URL 2 loginBeforeValidatiorUrl=https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest 3 #12306登錄驗證碼的地址 4 loginCode=https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand 5 #登錄URL 6 loginUrl=https://dynamic.12306.cn/otsweb/loginAction.do?method=login 7 #用戶名 8 username=xxxx 9 #密碼 10 password=xxx
通過返回的HTML,如果看到自己的名字,就說明登錄成功了。如果大家還想做什么動作,就可以發揮大家的想像力了。