注意:本文仅限学习交流,使用本文内容做任何违法的事情本人概不负责。
本人原创,转载请注明出处 http://blog.csdn.net/wang_situ/article/details/42262857
每年的这个时候,到了抢票季,在外面摸爬滚打了一年,终于有了时间回家一趟,可惜一票难求。
于是各种抢票软件(含流氓推广)、各种黄牛层出不穷。 加上最近12306在打击第三方抢票软件,使得买上
一张小小的火车票越来越难了。
在下不才,工作闲暇时间对12306进行了研究,收集了各种资料,决定用java写一个抢票软件。
不求比其他抢票软件好上多少,只求分享后,各路大神过来指点一二,一起完善。
-----------------------------------------------华丽的分割线----------------------------------------------------------
世上无难事,只要肯攀登
首先我们来看看现在12306的登录. 以及如何判断第三方软件。
工程截图
运行效果截图
Http 请求头分析
好了,现在开始贴代码了
1. https ssl处理类 (这个网上很多)
package cn.wangsitu.util.ssl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.HttpClientError;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ControllerThreadSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
public class MySecureProtocolSocketFactory implements
SecureProtocolSocketFactory {
private SSLContext sslContext = null;
public MySecureProtocolSocketFactory() {
}
private static SSLContext createEasySSLContext() {
try {
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { new MyX509TrustManager() },null);
return context;
} catch (Exception e) {
throw new HttpClientError(e.toString());
}
}
/**
*
* @return
*/
private SSLContext getSSLContext() {
if (this.sslContext == null) {
this.sslContext = createEasySSLContext();
}
return this.sslContext;
}
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(final String host, final int port,
final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException,
UnknownHostException, ConnectTimeoutException {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0) {
return createSocket(host, port, localAddress, localPort);
} else {
return ControllerThreadSocketFactory.createSocket(this, host, port,localAddress, localPort, timeout);
}
}
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host,port, autoClose);
}
}
package cn.wangsitu.util.ssl;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class MyX509TrustManager implements X509TrustManager {
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String)
*/
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String)
*/
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
*/
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
下面是核心登录
package cn.wangsitu.main;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import cn.wangsitu.util.ssl.MySecureProtocolSocketFactory;
import cn.wangsitu.util.ssl.MyX509TrustManager;
/**
* 12306 登录例子
*
* @author wang.situ
* @blog http://blog.csdn.net/wang_situ
* @date 2014-12-30
*/
public class LoginApplication {
/* 定义静态变量接收 登录成功的数据 后续[买票时]会用到 */
private static String cookieString;
private static String login_key;
private static String login_value;
public static void main(String[] args) throws Exception {
login("你的帐号", "你的密码");
}
/**
* 获取 支持 ssl的ThreadSafeClientConnmanager
* 12306用的是 https的协议,所以要做修改
*/
public static ThreadSafeClientConnManager getClientManager() throws Exception {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new MyX509TrustManager();
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
return mgr;
}
/**
* 登录逻辑
* @param username 明文帐号
* @param password 明文密码
**/
public static DefaultHttpClient login(String username,String password) throws Exception {
String url = "https://kyfw.12306.cn/otn/login/init";
ProtocolSocketFactory fcty = new MySecureProtocolSocketFactory();
Protocol.registerProtocol("https", new Protocol("https", fcty, 443));
/*初始cookie*/
String cookie = "_jc_save_detail=true; _jc_save_showZtkyts=true;";
DefaultHttpClient client = new DefaultHttpClient(getClientManager());
HttpGet get = new HttpGet(url);
get.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
get.addHeader("Accept-Language", "zh-CN,zh;q=0.8");
get.addHeader("Connection", "Keep-Alive");
get.addHeader("Cookie",cookie);
get.addHeader("Host", "kyfw.12306.cn");
get.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
HttpResponse response = client.execute(get);
String responseText = EntityUtils.toString(response.getEntity(), "GBK");
get.abort();
List list = client.getCookieStore().getCookies();
for(Cookie c : list){
cookie += c.getName()+"="+c.getValue()+";";
}
System.out.println("HttpHeader Cookie: "+cookie);
/* ------ 以上逻辑主要是获取cookie JSESSIONID 值 */
/* ------ https://kyfw.12306.cn/otn/login/init 返回的数据会动态的引入一个js 这里主要是截取
* 这个js很重要,主要是动态生成一个登录的key和value,没有的话,你的就是第三方非法软件
*
* */
int rindex = responseText.indexOf("otn/dynamicJs");
String jsurl = "";
String kkky = "";
String vvvy = "1111";
if(rindex!=-1){
String rtxt = responseText.substring(rindex,responseText.length());
int r33 = rtxt.indexOf("\"");
jsurl = "https://kyfw.12306.cn/"+rtxt.substring(0, r33);
HttpGet jsget = new HttpGet(jsurl);
jsget.addHeader("Accept","*/*");
jsget.addHeader("Connection","keep-alive");
jsget.addHeader("Cookie",cookie);
jsget.addHeader("Host","kyfw.12306.cn");
jsget.addHeader("Referer","https://kyfw.12306.cn/otn/login/init");
jsget.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
HttpResponse strRes = client.execute(jsget);
String strScr = EntityUtils.toString(strRes.getEntity(), "GBK");
int ri1 = strScr.indexOf("var key='");
strScr = strScr.substring(ri1,strScr.length());
int ri2 = strScr.indexOf("';");
kkky = strScr.substring(9,ri2);
jsget.abort();
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
String mygc = "function kkv(){return encode32(bin216(Base32.encrypt('"+vvvy+"', '"+kkky+"')));}function bin216(s){var i,l,o=\"\",n;s+=\"\";b=\"\";for(i=0,l=s.length;in))return null;n=m}for(var i=0;i>>8&0xff,data[i]>>>16&0xff,data[i]>>>24&0xff)}if(includeLength){return data.join('').substring(0,n)}else{return data.join('')}};function stringToLongArray(string,includeLength){var length=string.length;var result=[];for(var i=0;i>2]=string.charCodeAt(i)|string.charCodeAt(i+1)<<8|string.charCodeAt(i+2)<<16|string.charCodeAt(i+3)<<24}if(includeLength){result[result.length]=length}return result};this.encrypt=function(string,key){if(string==\"\"){return\"\"}var v=stringToLongArray(string,true);var k=stringToLongArray(key,false);if(k.length<4){k.length=4}var n=v.length-1;var z=v[n],y=v[0];var mx,e,p,q=Math.floor(6+52/(n+1)),sum=0;while(0>>2&3;for(p=0;p>>5^y<<2)+(y>>>3^z<<4)^(sum^y)+(k[p&3^e]^z);z=v[p]=v[p]+mx&0xffffffff}y=v[0];mx=(z>>>5^y<<2)+(y>>>3^z<<4)^(sum^y)+(k[p&3^e]^z);z=v[n]=v[n]+mx&0xffffffff}return longArrayToString(v,false)}};var keyStr=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";function encode32(input){input=escape(input);var output=\"\";var chr1,chr2,chr3=\"\";var enc1,enc2,enc3,enc4=\"\";var i=0;do{chr1=input.charCodeAt(i++);chr2=input.charCodeAt(i++);chr3=input.charCodeAt(i++);enc1=chr1>>2;enc2=((chr1&3)<<4)|(chr2>>4);enc3=((chr2&15)<<2)|(chr3>>6);enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else if(isNaN(chr3)){enc4=64}output=output+keyStr.charAt(enc1)+keyStr.charAt(enc2)+keyStr.charAt(enc3)+keyStr.charAt(enc4);chr1=chr2=chr3=\"\";enc1=enc2=enc3=enc4=\"\"}while(i params = new ArrayList();
params.add(new BasicNameValuePair("randCode",vcode));
params.add(new BasicNameValuePair("rand", "sjrand"));
params.add(new BasicNameValuePair("randCode_validate", ""));
get3.setEntity(new UrlEncodedFormEntity(params));
HttpResponse r3 = client.execute(get3);
String r3text = EntityUtils.toString(r3.getEntity(), "GBK");
if(r3text.indexOf("randCodeRight")!=-1){
client.execute(get3); //这里校验2次
System.out.println("验证码校验成功,正在登录...");
get3.abort();
/* ************* 登录 开始 ***************** */
String loginurl = "https://kyfw.12306.cn/otn/login/loginAysnSuggest";
HttpPost post1 = new HttpPost(loginurl);
post1.addHeader("Accept","*/*");
post1.addHeader("Accept-Language","zh-CN,zh;q=0.8");
post1.addHeader("Connection","keep-alive");
post1.addHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
post1.addHeader("Cookie",cookie);
post1.addHeader("Host","kyfw.12306.cn");
post1.addHeader("Origin","https://kyfw.12306.cn");
post1.addHeader("Referer","https://kyfw.12306.cn/otn/login/init");
post1.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
post1.addHeader("X-Requested-With","XMLHttpRequest");
List post1params = new ArrayList();
post1params.add(new BasicNameValuePair("loginUserDTO.user_name",username));
post1params.add(new BasicNameValuePair("userDTO.password", password));
post1params.add(new BasicNameValuePair("randCode",vcode));
post1params.add(new BasicNameValuePair("rand", "sjrand"));
System.out.println(kkky+" - "+vvvy);
/* 这个东西很重要,这个东西就是 动态的一个表单 */
post1params.add(new BasicNameValuePair(kkky, vvvy));
post1params.add(new BasicNameValuePair("myversion", "undefined"));
post1.setEntity(new UrlEncodedFormEntity(post1params));
HttpResponse r4 = client.execute(post1);
String ff = EntityUtils.toString(r4.getEntity(), "UTF-8");
post1.abort();
String successResult = "{\"validateMessagesShowId\":\"_validatorMessage\",\"status\":true,\"httpstatus\":200,\"data\":{\"loginCheck\":\"Y\"},\"messages\":[],\"validateMessages\":{}}";
if(ff.equals(successResult)){
System.out.println("登录成功 "+successResult);
cookieString = cookie;
login_key = kkky;
login_value = vvvy;
return client;
}else{
System.out.println("登录失败 "+ff);
}
}else{
System.out.println("验证码校验失败");
}
}catch(Exception ex){
ex.printStackTrace();
System.out.println("下载图片失败");
}
}
return null;
}
}
好了,大功告成 可以登录了。
目前登录偶尔会出现 截图所示:第三方购票...
本人原创,转载请注明出处 http://blog.csdn.net/wang_situ/article/details/42262857
功能逐渐完善中,后续会将代码放到github上