提要:html
登陆流程、所需参数可见以前博客(http://www.cnblogs.com/sei-cxt/p/8429069.html)。java
1. 注意点一:验证码。web
2. 注意点二:Cookie。编程
3. 示例代码和输出结果。浏览器
4. 之后要考虑的问题。缓存
1. 注意点一:验证码。cookie
首先是获取验证码,可知发送GET验证码请求后,12306服务端返回的是一张验证码图片,能够临时存在根目录下。同时,上篇博客(http://www.cnblogs.com/sei-cxt/p/8435921.html)写的GET方法是没有区分返回文件格式是image仍是text的,这个判断要加上。网络
获得的验证码图片以下,关于自动验证验证码的技术暂时不讨论,须要学习图像识别技术。咱们如今只说如何本身选择验证码并向服务端发送咱们选择的验证码。session
如上图,咱们发送的答案实际上是以图片左上顶点为原点的坐标,这一点经过fiddler抓包也能够发现,其中%2C是通过编码的“,”。app
编码缘由是:
Web设计人员面对的挑战之一是,要处理操做系统之间的区别。这些不一样会致使URL的问题:例若有些操做系统容许文件中有空格,而有些不容许。多数操做系统不反对文件名中出现#号,但在URL中#号表示文件名的结束,后面是片断标识符。其余特殊字符、非字母数字字符等等,在URL或另外一种操做系统中有特殊的意义,这也会产生相似的问题。为解决这些问题,URL使用的字符必须来自ASCII固定的子集,确切地讲,包括: ·大写字母A~Z ·小写字母a~z ·数字0~9 ·标点符号字符-_.!~*’和, …… 编码方式很是简单。除了前面指定的ASCII数字、字母和标点符号外,全部字符都要转换为字节,每一个字节要写为百分号后面加两个十六进制数字。空格是一种特殊状况,由于它太普通了。空格不是编码为%20,而是编码为加号(+)。加号自己编码为%2B。/ # = &和?字符在用于名时应当编码,而用于URL各部分的分隔符时不用编码。 ——《Java网络编程》第七章 Elliotte Rusty Harold著 朱涛江 林剑 译
所以咱们能够固定写死坐标,只用输入第几张图便可。
// 每一个图的位置,直接写死,之后可改 String[] pos = {"40,75", "112,75", "183,75", "255,75", "40,150", "112,150", "183,150", "255,150"};
2. 注意点二:Cookie。
没有Cookie,就没有办法实现登陆。由于登陆有三步,不一样步骤之间要告知信息,好比“已经成功验证验证码,能够验证登陆信息了”。
要是没有验证验证码,直接向登陆的网站发送用户名和密码,会返回:{"result_message":"验证码校验失败","result_code":"5"}
经过网上查询资料和核对fiddler请求头和响应头的数据得知,前一次的相应头里“Set-Cookie”字段能够用于下一步骤请求头的“Cookie”,这里有几个坑要注意一下。
1) 12306的“Set-Cookie”会发好几个,获得的数据是List<String>,直接用conn.getHeaderField("Set-Cookie")得到的cookie不全。由于getHeaderField(String name)这个方法只返回最后一次设置的值,返回类型是String(查API),必须用getHeaderFields()方法,从中找到key为"Set-Cookie"的数据而后用“; ”链接起来构成一个String字符串(尝试知有重复的数值对是容许的)。
2) 不能直接不链接得到的List<String>,而是屡次用conn.setRequestProperty("Cookie", cookie)发送。由于setRequestProperty(String param1, String param2)方法中,param1如果有重复,存的值会被param2覆盖(看源码)。
3. 示例代码和输出结果。
1) 工具类(HttpsRequest):
相较以前加入了POST方法,其与GET方法类似度很高,以后考虑合为一个方法。
GET方法里新加了区分返回类型的代码。
新添对Cookie的处理。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; public class HttpsRequest { public static HttpsRequestInfo methodGet(String urlStr, Map<String, Object> params, String cookie) { String realUrl = urlStr; StringBuffer responce = new StringBuffer(); StringBuffer set_cookie = new StringBuffer(); // GET方法须要将查询字符串拼接在url后面 if(params != null && params.size() != 0) { realUrl += encodeParam(params, "GET"); } try { // 链接url URL url = new URL(realUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // URL链接可用于输入和/或输出 // 若是打算使用 URL链接进行输出,则将DoOutput标志设置为true,默认值为false // 若是打算使用 URL链接进行输入,则将DoInput标志设置为true,默认值为true // conn.setDoOutput(false); // conn.setDoInput(true); // 容许链接使用任何可用的缓存,默认值为true // conn.setUseCaches(true); conn.setSSLSocketFactory(MyX509TrustManager.getSSLSocketFactory()); // 设置通常请求属性 conn.setRequestProperty("accept", "*/*"); // 客户端能够处理哪些数据类型 conn.setRequestProperty("connection", "Keep-Alive"); // 设置长链接,无过时时间 conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 使用的何种浏览器 if(cookie != null && !cookie.equals("")) { conn.setRequestProperty("Cookie", cookie); // 设置cookie,保证登陆 } // 创建通讯连接 conn.connect(); // 获取cookie Map<String, List<String>> headerMap = conn.getHeaderFields(); for(Entry<String, List<String>> entry : headerMap.entrySet()) { if(entry.getKey() != null && entry.getKey().equals("Set-Cookie")) { for(String str : entry.getValue()) { set_cookie.append(str + "; "); } } } // 判断返回格式 // getHeaderField(String name)只返回最后一次设置的值,因此找cookie的时候不能用 String type = conn.getHeaderField("Content-Type"); if(type.startsWith("image")) { // 保存验证码图片 InputStream is = conn.getInputStream(); FileOutputStream fos = new FileOutputStream("CAPTCHA.jpg"); int b = 0; while((b = is.read()) != -1) { fos.write(b); } is.close(); fos.close(); } else { // 读取response BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8")); String res = null; while((res = br.readLine()) != null) { responce.append(res); } br.close(); } } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return new HttpsRequestInfo(responce.toString(), set_cookie.toString()); } public static HttpsRequestInfo methodPost(String urlStr, Map<String, Object> params, String cookie) { StringBuffer responce = new StringBuffer(); StringBuffer set_cookie = new StringBuffer(); try { // 链接url URL url = new URL(urlStr); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); // URL链接可用于输入和/或输出 // 若是打算使用 URL链接进行输出,则将DoOutput标志设置为true,默认值为false // 若是打算使用 URL链接进行输入,则将DoInput标志设置为true,默认值为true conn.setDoOutput(true); // conn.setDoInput(true); // 容许链接使用任何可用的缓存,默认值为true // conn.setUseCaches(true); conn.setSSLSocketFactory(MyX509TrustManager.getSSLSocketFactory()); // 设置通常请求属性 conn.setRequestProperty("accept", "*/*"); // 客户端能够处理哪些数据类型 conn.setRequestProperty("connection", "Keep-Alive"); // 设置长链接,无过时时间 conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 使用的何种浏览器 if(cookie != null && !cookie.equals("")) { conn.setRequestProperty("Cookie", cookie); // 设置cookie,保证登陆 } // 创建通讯连接 conn.connect(); // POST须要发送请求的数据 if(params != null && params.size() != 0) { BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(conn.getOutputStream(), "utf-8")); bw.write(encodeParam(params, "POST")); bw.flush(); bw.close(); } // 获取cookie Map<String, List<String>> headerMap = conn.getHeaderFields(); for(Entry<String, List<String>> entry : headerMap.entrySet()) { if(entry.getKey() != null && entry.getKey().equals("Set-Cookie")) { for(String str : entry.getValue()) { set_cookie.append(str + "; "); } } } // 读取response BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8")); String res = null; while((res = br.readLine()) != null) { responce.append(res); } br.close(); } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return new HttpsRequestInfo(responce.toString(), set_cookie.toString()); } /** * 将查询的map形式的参数转码并拼成查询字符串 * @param params * @return */ private static String encodeParam(Map<String, Object> params, String method) { if(params == null || params.size() == 0) { return ""; } StringBuffer sb = null; if (method.equalsIgnoreCase("GET")) { sb = new StringBuffer("?"); } else if (method.equalsIgnoreCase("POST")) { sb = new StringBuffer(""); } for(Entry<String, Object> para : params.entrySet()) { try { sb.append(URLEncoder.encode(para.getKey(), "UTF-8")); sb.append("="); sb.append(URLEncoder.encode(para.getValue().toString(), "UTF-8")); sb.append("&"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } String query = sb.toString(); // 去掉最后一个& return query.substring(0, query.length() - 1); } /** * 选择获得验证码答案坐标 * @param choose * @return */ private static String getCaptchaAnswer(String choose) { // 每一个图的位置,直接写死,之后可改 String[] pos = {"40,75", "112,75", "183,75", "255,75", "40,150", "112,150", "183,150", "255,150"}; String[] chooses = choose.split(","); StringBuffer answer = new StringBuffer(); for(String cho : chooses) { answer.append(pos[Integer.parseInt(cho) - 1] + ","); } String ans = answer.toString(); return ans.substring(0, ans.length() - 1); } }
2) 证书相关类(MyX509TrustManager):与以前没有变化,不贴。
3) 网络信息类(HttpsRequestInfo):用来保存相应返回的信息和Cookie,原先的设计不能返回两个字符串。
public class HttpsRequestInfo { private String responce; private String cookie; public HttpsRequestInfo() { } public HttpsRequestInfo(String responce, String cookie) { super(); this.responce = responce; this.cookie = cookie; } public String getResponce() { return responce; } public void setResponce(String responce) { this.responce = responce; } public String getCookie() { return cookie; } public void setCookie(String cookie) { this.cookie = cookie; } }
4) main函数:验证验证码的响应头传回来的Cookie为空,所以登陆传用户名密码的时候只能用发验证码图片的时候传回来的Cookie,具体缘由之后考虑。
public static void main(String[] args) { Map<String, Object> params1 = new LinkedHashMap<String, Object>(); params1.put("login_site", "E"); params1.put("module", "login"); params1.put("rand", "sjrand"); HttpsRequestInfo info1 = HttpsRequest.methodGet("https://kyfw.12306.cn/passport/captcha/captcha-image", params1, null); System.out.println(info1.getResponce()); System.out.println(info1.getCookie()); Scanner in = new Scanner(System.in); String line = in.nextLine(); in.close(); Map<String, Object> params2 = new LinkedHashMap<String, Object>(); params2.put("answer", HttpsRequest.getCaptchaAnswer(line)); params2.put("login_site", "E"); params2.put("rand", "sjrand"); HttpsRequestInfo info2 = HttpsRequest.methodPost("https://kyfw.12306.cn/passport/captcha/captcha-check", params2, info1.getCookie()); System.out.println(info2.getResponce()); System.out.println(info2.getCookie()); Map<String, Object> params3 = new LinkedHashMap<String, Object>(); params3.put("username", "******"); params3.put("password", "******"); params3.put("appid", "otn"); HttpsRequestInfo info3 = HttpsRequest.methodPost("https://kyfw.12306.cn/passport/web/login", params3, info1.getCookie()); System.out.println(info3.getResponce()); System.out.println(info3.getCookie()); }
5) 输出结果:
BIGipServerpool_passport=300745226.50215.0000; path=/; _passport_ct=3a6f7d3cb29b4bd7b9ac3da2c41a8588t5927; Path=/passport; _passport_session=5a55bcfaa5644450b24d4f2ed8f1e7840821; Path=/passport; 2,7 {"result_message":"验证码校验成功","result_code":"4"} {"result_message":"登陆成功","result_code":0,"uamtk":"nrsMZEFcdXx8EK1B_n9ODPC7-ErURBJGrANQivMxnVc091210"} uamtk=nrsMZEFcdXx8EK1B_n9ODPC7-ErURBJGrANQivMxnVc091210; Path=/passport; _passport_ct=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/passport;
4. 之后要考虑的问题。
返回信息之后要用JSON解析,而且要判断result_code来决定以后的操做。