在正式开始之前,先来看看浏览器与服务器的通讯过程
浏览器给服务器发送请求连接,服务器接收到浏览器发送的请求,在服务器端生成一个对应的标示(Session),并且返回这个标识的id(Cookie)给浏览器保存,以后浏览器每一次的请求都带着这个cookie,这样服务器就可以对应到相应的用户了
登录流程
第一步:访问教务处网址(本人教务处网址为http://218.197.80.27/),教务处服务器会返回一个cookie,作为你的标识
第二步:请求验证码(验证码网址见下图),带着cookie去请求,这样服务器就知道这个验证码对应的用户是谁
第三步:提交数据给服务器,包括用户名,密码,验证码,还有一个特殊字段_VIEWSTATE(下面讲解),cookie(用于标示用户)
经历过上面的3步,我们就登录到系统了,也就是我们的cookie对于服务器来说,就是已经登录的了,通过这个cookie可以获取课表等浏览器上的操作
实际编码
上面讲了一大堆原理性的东西,其实就是使用程序去模拟浏览器的行为,向服务器请求和发送数据,下面讲解实际编码
- 获取cookie
URL url = new URL("http://218.197.80.27/"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //设置请求超时时间 conn.setReadTimeout(1000); if (conn.getResponseCode() != 200) { System.out.println("error"); return; } //获取Set-Cookie String str = conn.getHeaderField("Set-Cookie"); int begin = str.indexOf("ASP.NET_SessionId"); int end = str.indexOf(';', begin); String cookie = str.substring(begin, end); System.out.println(cookie); //输出为:ASP.NET_SessionId=j420uwvbnagokt45lxhx2v55
- 获取__VIEWSTATE
关于_VIEWSTATE的作用请自行百度,这个本人测试时必须随着用户名密码等一起提交
_VIEWSTATE存在于首页的源码中,打开http://218.197.80.27/,右键查看源代码,搜索,_VIEWSTATE,即可看到他的值,现在我们通过代码来获取,由于在获取Cookie的时候,我们访问了首页,所以这里可以把两个操作合二为一
public void getCookie() { try { URL url = new URL("http://218.197.80.10/"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 设置请求超时时间 conn.setReadTimeout(1000); if (conn.getResponseCode() != 200) { System.out.println("error"); return; } // 获取Set-Cookie String str = conn.getHeaderField("Set-Cookie"); int begin = str.indexOf("ASP.NET_SessionId"); int end = str.indexOf(';', begin); String cookie = str.substring(begin, end); System.out.println(cookie); BufferedReader read = new BufferedReader(new InputStreamReader(conn.getInputStream())); //读取整个网页的源码 StringBuffer buffer = new StringBuffer(); while ((str = read.readLine()) != null) { buffer.append(str); } read.close(); begin = buffer.indexOf("__VIEWSTATE\""); begin = buffer.indexOf("value=\"", begin + 1); begin = buffer.indexOf("\"", begin + 1); end = buffer.indexOf("\"", begin + 1); viewstate = buffer.substring(begin + 1, end); System.out.println("隐藏字段 = " + viewstate); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } //输出为: //ASP.NET_SessionId=4kusfii0urpbrazkhxvuas45 //隐藏字段 = dDwyODE2NTM0OTg7Oz74kxBxGi3w7jUfyZCkgy/B+RGrKQ==
- 获取验证码
一定要在请求头中设置cookie,不然服务器无法知道这个验证码对应的是哪一个用户发送过来的
保存验证码就是访问验证码对应的网址,在浏览器中,右键验证码 即可选择复制图片网址,这里我们保存到本地的d:\\img.gif
/** * 存储验证码到本地 */ public void SaveImg() { try { /** * img变量的值为http://218.197.80.27/CheckCode.aspx * 也就是上面图片中,验证码的网址,在浏览器中,右键验证码 即可选择复制图片网址 */ URL url = new URL(img); HttpURLConnection openConnection = (HttpURLConnection) url.openConnection(); openConnection.setRequestMethod("GET"); openConnection.setReadTimeout(5000); // cookie一同提交(ASP.NET_SessionId=4kusfii0urpbrazkhxvuas45,只需要等号后面的一串数据) openConnection.setRequestProperty("Cookie", cookie); InputStream in = openConnection.getInputStream(); //必须使用字节流,不能使用字符流,不然图片无法打开 byte[] by = new byte[50000]; // 将验证码保存到本地 FileOutputStream file = new FileOutputStream("d:\\img.gif"); int len = 0; while ((len = in.read(by)) != -1) { file.write(by, 0, len); } file.close(); in.close(); System.out.println("读取验证码完毕!"); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
读取验证码成功
- 登录方正管理系统
刚才我们已经获取了cookie,验证码,_VIEWSTATE,是不是觉得有点小激动呢?现在就用获取到的数据来登录教务系统
什么情况下是登录成功?这是一个问题,在我们的程序中,如何判断登录成功了
在请求登录的情况中,如果服务器的返回码为302则为登录成功(通知我们跳转到登录以后的界面),如果为200则代码登录失败,大家可以尝试着用火狐浏览器自带的抓包工具查看两种情况下的返回码
提交的数据中存在一个用户类型,就是上面截图中乱码的字段RadioButtonList1,这个是由于编码的不同造成的,在服务器的相应头中,我们可以看到服务器采用的编码为gb2312,所以我们在拼接参数的时候使用URLEncoder.encode()方法进行编码
/** * 登陆 * @throws IOException */ public void load() throws IOException { System.out.print("请输入验证码:"); Scanner in = new Scanner(System.in); check = in.nextLine(); //输入保存到本地的验证码图片上的验证码 System.out.println("check = " + check); //拼接请求字符串 String str = "__VIEWSTATE=" + URLEncoder.encode(viewstate, "gb2312") + "&txtUserName=" + id + "&TextBox2=" + password + "&txtSecretCode=" + check + "&RadioButtonList1=" + URLEncoder.encode("学生", "gb2312") + "&Button1=&lbLanguage=&hidPdrs=&hidsc="; ; System.out.println("参数列表:" + str); //登录提交的网址 URL url = new URL("http://218.197.80.10/default2.aspx"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //Post提交 conn.setRequestMethod("POST"); conn.setReadTimeout(5000); conn.setUseCaches(false); //禁止程序自己跳转到目标网址,必须设置,不然程序会自己响应 //302返回码,自己请求跳转后的网址,出现Object Had Moved!错误 conn.setInstanceFollowRedirects(false); // 写入cookie conn.setRequestProperty("Cookie", cookie); conn.setDoOutput(true); OutputStream out = conn.getOutputStream(); //写入参数 out.write(str.getBytes()); out.close(); //打印返回码 System.out.println("返回码:" + conn.getResponseCode()); //打印服务器返回的跳转网址 System.out.println("Location :" + conn.getHeaderField("Location")); BufferedReader read = new BufferedReader(new InputStreamReader(conn.getInputStream())); String temp; //读取页面源码 StringBuffer ab = new StringBuffer(); while ((temp = read.readLine()) != null) { ab.append(temp); } if (conn.getResponseCode() != 302) { //getError为自定义函数,提取出错误信息 System.out.println(getError(ab.toString())); return; } } // 请输入验证码:j5jj // check = j5jj // 参数列表:__VIEWSTATE=dDwyODE2NTM0OTg7Oz74kxBxGi3w7jUfyZCkgy%2FB%2BRGrKQ%3D%3D&txtUserName=13150227&TextBox2=*****(密码)&txtSecretCode=j5jj&RadioButtonList1=%D1%A7%C9%FA&Button1=&lbLanguage=&hidPdrs=&hidsc= // 返回码:302
大家可以获取登录失败以后的页面,查看源码,不难找到错误提示的位置
/** * 打印登陆错误信息 * * @param html * @return */ private String getError(String html) { Pattern pattern = Pattern.compile("alert.*?\\)"); Matcher matcher = pattern.matcher(html); if (!matcher.find()) return "未定义错误"; return html.substring(matcher.start() + 7, matcher.end() - 2); }
经过以上的步骤,我们就正式登录到方正教务系统了,通过火狐浏览器自带的抓包工具,我们可以很轻松的获取浏览器提交的数据以及服务器返回的数据,然后用程序模拟这一过程,就可以获取到自己想要的信息了,获取课表就在下一篇日志里面讲吧
温馨提示:编码不易,且编且珍惜