转:HttpClient模拟登录12306购票网站
首先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);
具体代码如下:
package org.study.meteor.ticket.util; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; /** * MySecureProtocolSocketFactory.java.java Create on 2012-9-26下午1:15:03 * * * Copyright (c) 2012 by MTA. * * @author lmeteor * @Email [email protected] * @description 自定义的socket factory 实现自动接受证书 * @version 1.0 */ public class MySecureProtocolSocketFactory implements SecureProtocolSocketFactory { private SSLContext sslcontext = null; private SSLContext createSSLContext() { SSLContext sslcontext = null; try { sslcontext = SSLContext.getInstance("SSL"); sslcontext.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return sslcontext; } private SSLContext getSSLContext() { if (this.sslcontext == null) { this.sslcontext = createSSLContext(); } return this.sslcontext; } public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose); } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(host, port); } public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort); } public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { if (params == null) { throw new IllegalArgumentException("Parameters may not be null"); } int timeout = params.getConnectionTimeout(); SocketFactory socketfactory = getSSLContext().getSocketFactory(); if (timeout == 0) { return socketfactory.createSocket(host, port, localAddress, localPort); } else { Socket socket = socketfactory.createSocket(); SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); SocketAddress remoteaddr = new InetSocketAddress(host, port); socket.bind(localaddr); socket.connect(remoteaddr, timeout); return socket; } } // 自定义私有类 private static class TrustAnyTrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } } }
下面的是httpClient的具体实现类:
package org.study.meteor.ticket.util; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.protocol.Protocol; /** * HttpDoPostUtils.java Create on 2012-9-7下午3:08:18 * * * Copyright (c) 2012 by MTA. * * @author lmeteor * @Email [email protected] * @description 模拟HTTP发送请求得到报文 * @version 1.0 */ @SuppressWarnings("deprecation") public class HttpDoPostUtils { private static HttpClient httpClient = null; static { //指定协议名称和默认的端口号 Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory(), 443); //注册刚才创建的https 协议对象 Protocol.registerProtocol("https", myhttps); httpClient = new HttpClient(); } /** * 发送请求报文,得到响应报文 * @param url * 登录请求URL * @param pList * 是否包含请求参数 * @return * @throws UnsupportedEncodingException */ public static String doRequestToString(String url,List<NameValuePair> pList) throws UnsupportedEncodingException { //获得postMethod对象 PostMethod pmethod = getPostMethod(url); pmethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8"); //判断是否包含参数 if(null != pList && pList.size() > 0) { pmethod.setRequestBody(pList.toArray(new NameValuePair[pList.size()])); } String value = ""; try { httpClient.executeMethod(pmethod); value = pmethod.getResponseBodyAsString(); } catch ( HttpException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } return value; } /** * 获得12306网站的登录验证码 * @param url * 请求URL * @param filePath * 验证码保存路径 如:e:\\login.jpg * @return */ public static File doGetFile(String url,String filePath) { PostMethod pmethod = getPostMethod(url); pmethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8"); try { httpClient.executeMethod(pmethod); //得到响应中的流对象 InputStream in = pmethod.getResponseBodyAsStream(); //包装 并读出流信息 BufferedInputStream bis = new BufferedInputStream(in); File file = new File(filePath); FileOutputStream fs = new FileOutputStream(file); byte[] buf = new byte[1024]; int len = bis.read(buf); if(len == -1 || len == 0){ file.delete(); file = null; } while (len != -1) { fs.write(buf, 0, len); len = bis.read(buf); } fs.flush(); fs.close(); return file; } catch (HttpException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } public static List<NameValuePair> createNameValuePair(String params) { List<NameValuePair> nvps = new ArrayList<NameValuePair>(); if (null != params && !params.trim().equals("")) { String[] _params = params.split("&"); // userCookieList = new AttributeList(); for (int i = 0; i < _params.length; i++) { int _i = _params[i].indexOf("="); if (_i != -1) { String name = _params[i].substring(0, _i); String value = _params[i].substring(_i + 1); nvps.add(new NameValuePair(name, value)); } } } return nvps; } public static PostMethod getPostMethod(String url) { PostMethod pmethod = new PostMethod(url); //设置响应头信息 pmethod.addRequestHeader("Connection", "keep-alive"); pmethod.addRequestHeader("Cache-Control", "max-age=0"); pmethod.addRequestHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); pmethod.addRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); return pmethod; } }
模拟请求的类已经出来,现在开始进行模拟登录,登录前必须知道12306自身是怎样提交请求,并包含哪些参数到后台,通过firebug就很容易找到这些东西了。
12306登录之前会调用“https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest”,后台返回一串JSON报文,如下:
当randError为“Y”时,才对表单FORM提交,并且将loginRand的值初始化到表单里的隐藏域中,作为参数传到后台
现在最后一步就是拼接参数了,具体操作代码如下:
package org.study.meteor.ticket.util; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import org.study.meteor.ticket.domain.LoginBeforeValidatior; import net.sf.json.JSONObject; /** * Login.java.java Create on 2012-9-26下午1:48:42 * * * Copyright (c) 2012 by MTA. * * @author lmeteor * @Email [email protected] * @description * @version 1.0 */ public class Login { /** * 获取验证码 * @param filePath * @return */ public static String getRandCode(String filePath) { String randCode = ""; /** 获取验证码 */ HttpDoPostUtils.doGetFile(PropertiesUtils.newInstance().getPropertiesValue("loginCode"),filePath); randCode = readString("请输入登录验证码:"); return randCode; } /** * 实现登录操作 * @throws UnsupportedEncodingException */ public static void doLogin() throws UnsupportedEncodingException { String randCode = getRandCode("e:\\login.jpg"); /** 登录前 提交得到报文 */ String loginBeforeVal = HttpDoPostUtils.doRequestToString(PropertiesUtils.newInstance().getPropertiesValue("loginBeforeValidatiorUrl"),null); //将返回的JSON报文转换成指定的对象 JSONObject jsonObj = JSONObject.fromObject(loginBeforeVal); LoginBeforeValidatior loginBefore = new LoginBeforeValidatior(); loginBefore = (LoginBeforeValidatior) JSONObject.toBean(jsonObj, LoginBeforeValidatior.class); //拼接参数 StringBuffer params = new StringBuffer(); params.append("loginRand="+loginBefore.getLoginRand()).append("&") .append("refundLogin=N").append("&") .append("refundFlag=Y").append("&") .append("loginUser.user_name="+PropertiesUtils.newInstance().getPropertiesValue("username")).append("&") .append("nameErrorFocus=&") .append("user.password="+PropertiesUtils.newInstance().getPropertiesValue("password")).append("&") .append("passwordErrorFocus=&") .append("randCode="+randCode).append("&") .append("randErrorFocus="); //像服务器发送登录请求 并返回对应的报文 String loginResponseText = HttpDoPostUtils.doRequestToString(PropertiesUtils.newInstance().getPropertiesValue("loginUrl"),HttpDoPostUtils.createNameValuePair(params.toString())); System.out.println(loginResponseText); } /** * 多控制台读取验证码 * @param msg * @return * @throws Exception */ private static String readString(String msg) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); try{ System.out.print(msg+": "); return bufferedReader.readLine(); }catch(Exception e){ } return "1245"; } public static void main(String[] args) throws UnsupportedEncodingException { //Login login = new Login(); //login.doLogin(); Login.doLogin(); } }
URL都是在配置文件中的,大致如下:
#12306登录之前调用URL loginBeforeValidatiorUrl=https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest #12306登录验证码的地址 loginCode=https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand #登录URL loginUrl=https://dynamic.12306.cn/otsweb/loginAction.do?method=login #用户名 username=xxxx #密码 password=xxx
通过返回的HTML,如果看到自己的名字,就说明登录成功了。如果大家还想做什么动作,就可以发挥大家的想像力了。
只作为学习使用!
转自:http://www.cnblogs.com/lmeteor/archive/2012/09/27/2705458.html
相关推荐
创建一个 HttpClient 实例,这个实例需要调用 Dispose 方法释放资源,这里使用了 using 语句。接着调用 GetAsync,给它传递要调用的方法的地址,向服务器发送 Get 请求。