模拟登陆新版正方教务管理系统
写在前面
已经过了一年半,网站有所改版,目前是可以正常登录的,获取信息的那几个url不是直接返回JSON了,需要自己去解析数据。
引入
与很多高校一样,西安皇家邮电大学一直钟爱于正方教务管理系统。
前一段时间爬了学校的旧版的教务系统,旧版主页
想了解更多请点击这里:爬取学校教务系统获取学生信息
不知道学校是不是把装空调的拿去买新教务系统了,新教务系统主页
可以看出,学校用的仍然是正方教务系统,不过版本升级了,新教务系统是用Java写的后台,前端用了Bootstrap框架。与旧系统相比,界面美观大方,易用性也明显提高。
准备
模拟登陆前,我们首先要清楚需要提交那些数据。
打开F12,我们在访问主页时,除了加载了JS的HTML还向服务器发送了一个get请求。
直接访问它可以获取一个json:
{"exponent":"AQAB","modulus":"AIMNm8zi5XkWLRDUy7w6bjypS+d8ng7an00UYH8UZMhUvrDAvpuifsFu+rU6dmMFQHpHGo9ZlyEy9GWy6ev3s8ro15869OIKo\/nwexEnb8AD0DO7NaV9jzDjqRjEldAs5ct9pdjo7SxQccJYfSbFbwb6206w1q4EAwlFDvTMJfKj"}
每次获取的内容都不一样,关于这个东西的作用下面再说。
点击登陆时,可以看到向服务器发送了一个POST请求,data域中包含如下数据:
csrftoken为了防止跨站域请求伪造
yhm为输入的用户名
mm并不是我们输入的密码
通过审查主页的元素,可以找到csrftoken(每次都不一样)
与此同时,发现结尾有很多JS文件
还记得我们表单中mm很奇怪吧,那是因为明文被加密过了,加密的方式是RSA,这些js文件就是完成了加密的操作。在login.js可以发现下面几个关键:
// 获取公钥
$.getJSON(_path+"/xtgl/login_getPublicKey.html?time="+new Date().getTime(),function(data){modulus = data["modulus"];exponent = data["exponent"];
});
......
// 创建公钥
var rsaKey = new RSAKey();
rsaKey.setPublic(b64tohex(modulus), b64tohex(exponent));
// 对密码加密
var enPassword = hex2b64(rsaKey.encrypt($("#mm").val()));
$("#mm").val(enPassword);
$("#hidMm").val(enPassword);
加密后的密码要转化为base64的形式填充到data域中。
在登录成功后,我们可以尝试去获取相关信息。通过分析,可以发现获取这些信息的URL和所需要的Data域:
获取学籍信息:
获取课表信息
获取成绩
这里面获取信息的请求返回值都是JSON,可以根据需要,把所需要的数据解析出来。
最后要注意,访问每一个请求别忘携带Cookies。
操作
明白了登录的原理,我们来梳理一下步骤:
- 获取csrftoken和Cookies
- 请求获取PublicKey
- 利用PublicKey对登录密码加密并用Base64编码
- 进行登录
- 获取所需要的信息
看起来很顺畅的思路,但我遇到了很大的问题,主要是在对密码加密的时候,Java与JavaScript在对数据进行RSA加密有些区别:
JavaScript在加密前对数据进行了随机填充,并用RSA/None/NoPadding的填充方式来加密,每一次得到的每一次结果都不同;Java在RSA加密时默认的填充方式为RSA_PKCS1_PADDING。据说可以在Java中用第三方包来实现NoPadding的填充方式,但是我在Java使用Bouncycastle提供的NoPadding填充方式初始化公钥不成功,提示我:
RSA modulus has a small prime factor
在Java中直接运行JS文件,简单的JS还可以,如果有的JS文件中会有navigator、window,javax.script.ScriptEngine是无法解析的。
最终选择用Java将JavaScript前端加密方式实现。
public class ConnectJWGL {private final String url = "http://www.zfjw.xupt.edu.cn";private Map<String,String> cookies = new HashMap<>();private String modulus;private String exponent;private String csrftoken;private Connection connection;private Connection.Response response;private Document document;private String stuNum;private String password;public ConnectJWGL(String stuNum,String password){this.stuNum = stuNum;this.password = password;}public void init() throws Exception{getCsrftoken();getRSApublickey();beginLogin();}// 获取csrftoken和Cookiesprivate void getCsrftoken(){try{connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_slogin.html?language=zh_CN&_t="+new Date().getTime());connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");response = connection.timeout(5000).execute();cookies = response.cookies();document = Jsoup.parse(response.body());csrftoken = document.getElementById("csrftoken").val();}catch (Exception ex){ex.printStackTrace();}}// 获取公钥并加密密码private void getRSApublickey() throws Exception{connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_getPublicKey.html?" +"time="+ new Date().getTime());connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");response = connection.cookies(cookies).ignoreContentType(true).timeout(5000).execute();JSONObject jsonObject = JSON.parseObject(response.body());modulus = jsonObject.getString("modulus");exponent = jsonObject.getString("exponent");password = RSAEncoder.RSAEncrypt(password, B64.b64tohex(modulus), B64.b64tohex(exponent));password = B64.hex2b64(password);}//登录public boolean beginLogin() throws Exception{connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_slogin.html");connection.header("Content-Type","application/x-www-form-urlencoded;charset=utf-8");connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");connection.data("csrftoken",csrftoken);connection.data("yhm",stuNum);connection.data("mm",password);connection.data("mm",password);connection.cookies(cookies).ignoreContentType(true).method(Connection.Method.POST).execute();response = connection.execute();document = Jsoup.parse(response.body());if(document.getElementById("tips") == null){System.out.println("登陆成功");return true;}else{System.out.println(document.getElementById("tips").text());return false;}}
}
RSA加密
public class RSAEncoder {private static BigInteger n = null;private static BigInteger e = null;public static String RSAEncrypt(String pwd, String nStr, String eStr){n = new BigInteger(nStr,16);e = new BigInteger(eStr,16);BigInteger r = RSADoPublic(pkcs1pad2(pwd,(n.bitLength()+7)>>3));String sp = r.toString(16);if((sp.length()&1) != 0 )sp = "0" + sp;return sp;}private static BigInteger RSADoPublic(BigInteger x){return x.modPow(e, n);}private static BigInteger pkcs1pad2(String s, int n){if(n < s.length() + 11) { // TODO: fix for utf-8System.err.println("Message too long for RSAEncoder");return null;}byte[] ba = new byte[n];int i = s.length()-1;while(i >= 0 && n > 0) {int c = s.codePointAt(i--);if(c < 128) { // encode using utf-8ba[--n] = new Byte(String.valueOf(c));}else if((c > 127) && (c < 2048)) {ba[--n] = new Byte(String.valueOf((c & 63) | 128));ba[--n] = new Byte(String.valueOf((c >> 6) | 192));} else {ba[--n] = new Byte(String.valueOf((c & 63) | 128));ba[--n] = new Byte(String.valueOf(((c >> 6) & 63) | 128));ba[--n] = new Byte(String.valueOf((c >> 12) | 224));}}ba[--n] = new Byte("0");byte[] temp = new byte[1];Random rdm = new Random(47L);while(n > 2) { // random non-zero padtemp[0] = new Byte("0");while(temp[0] == 0)rdm.nextBytes(temp);ba[--n] = temp[0];}ba[--n] = 2;ba[--n] = 0;return new BigInteger(ba);}
}
Base64与十六进制的相互转化
public class B64 {public static String b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";private static char b64pad = '=';private static String hexCode = "0123456789abcdef";// 获取对应16进制字符public static char int2char(int a){return hexCode.charAt(a);}// Base64转16进制public static String b64tohex(String s) {String ret = "";int k = 0;int slop = 0;for(int i = 0; i < s.length(); ++i) {if(s.charAt(i) == b64pad) break;int v = b64map.indexOf(s.charAt(i));if(v < 0) continue;if(k == 0) {ret += int2char(v >> 2);slop = v & 3;k = 1;}else if(k == 1) {ret += int2char((slop << 2) | (v >> 4));slop = v & 0xf;k = 2;}else if(k == 2) {ret += int2char(slop);ret += int2char(v >> 2);slop = v & 3;k = 3;}else {ret += int2char((slop << 2) | (v >> 4));ret += int2char(v & 0xf);k = 0;}}if(k == 1)ret += int2char(slop << 2);return ret;}// 16进制转Base64public static String hex2b64(String h) {int i , c;StringBuilder ret = new StringBuilder();for(i = 0; i+3 <= h.length(); i+=3) {c = parseInt(h.substring(i,i+3),16);ret.append(b64map.charAt(c >> 6));ret.append(b64map.charAt(c & 63));}if(i+1 == h.length()) {c = parseInt(h.substring(i,i+1),16);ret.append(b64map.charAt(c << 2));}else if(i+2 == h.length()) {c = parseInt(h.substring(i,i+2),16);ret.append(b64map.charAt(c >> 2));ret.append(b64map.charAt((c & 3) << 4));}while((ret.length() & 3) > 0) ret.append(b64pad);return ret.toString();}
}
获取学籍信息、课表、成绩
// 获取学籍信息public void getStudentInformaction() throws Exception {connection = Jsoup.connect(url+ "/jwglxt/xsxxxggl/xsxxwh_cxCkDgxsxx.html?gnmkdm=N100801&su="+ stuNum);connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");response = connection.cookies(cookies).ignoreContentType(true).execute();JSONObject jsonObject = JSON.parseObject(response.body());System.out.println("--- 基本信息 ---");System.out.println("学号:" + jsonObject.getString("xh_id"));System.out.println("性别:" + jsonObject.getString("xbm"));System.out.println("民族:" + jsonObject.getString("mzm"));System.out.println("学院:" + jsonObject.getString("jg_id"));System.out.println("班级:" + jsonObject.getString("bh_id"));System.out.println("专业:" + jsonObject.getString("zszyh_id"));System.out.println("状态:" + jsonObject.getString("xjztdm"));System.out.println("入学年份:" + jsonObject.getString("njdm_id"));System.out.println("证件号码:" + jsonObject.getString("zjhm"));System.out.println("政治面貌:" + jsonObject.getString("zzmmm"));}// 获取课表信息public void getStudentTimetable(int year , int term) throws Exception {connection = Jsoup.connect(url+ "/jwglxt/kbcx/xskbcx_cxXsKb.html?gnmkdm=N2151");connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");connection.data("xnm",String.valueOf(year));connection.data("xqm",String.valueOf(term * term * 3));response = connection.cookies(cookies).method(Connection.Method.POST).ignoreContentType(true).execute();JSONObject jsonObject = JSON.parseObject(response.body());if(jsonObject.get("kbList") == null){System.out.println("暂时没有安排课程");return;}JSONArray timeTable = JSON.parseArray(jsonObject.getString("kbList"));System.out.println(String.valueOf(year) + " -- " + String.valueOf(year + 1) + "学年 " + "第" + term + "学期");for (Iterator iterator = timeTable.iterator(); iterator.hasNext();) {JSONObject lesson = (JSONObject) iterator.next();System.out.println(lesson.getString("xqjmc") + " " +lesson.getString("jc") + " " +lesson.getString("kcmc") + " " +lesson.getString("xm") + " " +lesson.getString("xqmc") + " " +lesson.getString("cdmc") + " " +lesson.getString("zcd"));}}// 获取成绩信息public void getStudentGrade(int year , int term) throws Exception {Map<String,String> datas = new HashMap<>();datas.put("xnm",String.valueOf(year));datas.put("xqm",String.valueOf(term * term * 3));datas.put("_search","false");datas.put("nd",String.valueOf(new Date().getTime()));datas.put("queryModel.showCount","20");datas.put("queryModel.currentPage","1");datas.put("queryModel.sortName","");datas.put("queryModel.sortOrder","asc");datas.put("queryModel.sortName","");datas.put("time","0");connection = Jsoup.connect(url+ "/jwglxt/cjcx/cjcx_cxDgXscj.html?gnmkdm=N305005&layout=default&su=" + stuNum);connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");response = connection.cookies(cookies).method(Connection.Method.POST).data(datas).ignoreContentType(true).execute();connection = Jsoup.connect(url+ "/jwglxt/cjcx/cjcx_cxDgXscj.html?doType=query&gnmkdm=N305005");connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");response = connection.cookies(cookies).method(Connection.Method.POST).data(datas).ignoreContentType(true).execute();JSONObject jsonObject = JSON.parseObject(response.body());JSONArray gradeTable = JSON.parseArray(jsonObject.getString("items"));for (Iterator iterator = gradeTable.iterator(); iterator.hasNext();) {JSONObject lesson = (JSONObject) iterator.next();System.out.println(lesson.getString("kcmc") + " " +lesson.getString("jsxm") + " " +lesson.getString("bfzcj") + " " +lesson.getString("jd"));}}
完整代码请点击这里:Semi-automatic-Crawl-JWGL (欢迎Fork、提Issues)
写在最后
这个小东西也花了我好长时间,主要还是卡在RSA加密那一块,我一开始并不知道不同平台对RSA的实现方式不同,也不知到RSA有多种填充方式,还需要多研究一下这方面的问题。然后这个小东西目前仅仅只是登陆,关于信息的获取与分析接下来也会着手去做的。
参考
CUMT教务系统模拟登录
RSA加密算法 - - 维基百科
javascript使用RSA加密提交数据
RSA使用js加密,使用java解密
新浪微博数据抓取(java实现)
模拟登陆新版正方教务管理系统相关推荐
- 模拟登陆新版正方教务管理系统【可以获取学生基本/课表信息】
写在前面 博主登陆现在还可以正常使用,但是后面登陆成功,获取信息啥的有问题 登陆还是按照学长的来,模拟登陆新版正方教务管理系统 开始 学校教务系统改版,我直接copy博主代码获取学籍那里一直是获取到的 ...
- 教务系统自动评教_新版正方教务管理系统自动评教脚本
本脚本适用于新版正方教务管理系统学生评教页面. 使用注意: 1,本脚本必须在Chrome内核浏览器使用,其余内核(IE,Edge等)无法使用! 2,如果您使用的是360浏览器/QQ浏览器/百度浏览器等 ...
- 新版正方教务管理系统API(获取课程表,考试,成绩,通知,自动抢课等)
一个有关新版正方教务管理系统(如下图展示的主页面即为新版教务系统)的API,可以实现教务系统内基础的查询功能,未来还会添加选课抢课,一键评价等功能. 已实现与待实现 自动登陆.cookies获取 个人 ...
- JSoup模拟登录新版正方教务系统(内网-教务系统)获取信息过程详解
新版正方教务系统登录界面: 目录 一.需求分析 二.模拟登录内网 三.模拟登录教务系统 四.爬取成绩和课表信息 参考文章 一.需求分析 需要访问教务系统,爬取出课表成绩等信息,并在自己所写的APP ...
- 用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)
前言 最近做一个微信小程序,需要登录教务系统.提前用python尝试一下登录接口,并获取到课表打印出来. 我们学校用到新版正方教务系统,长这个样子. 相比旧版的教务系统,唯一好处是不用输入二维码方便爬 ...
- 爬取正方教务管理系统获取学生信息
新版正方教务系统请点这里:模拟登陆新版正方教务管理系统(获取学籍信息.课表和成绩) 最近想学点爬虫玩玩,拿学校的教务系统练练手.学校与很多高校一样,用的是正方教务管理系统,非常的不好用,经常出现登陆不 ...
- go语言爬取新版正方教务系统数据
go语言爬取新版正方教务系统数据 学完go语言的基础之后已经过了一个多月了,开始想试着利用些时间写写博客,就打算将半个月前练着写的一个go爬虫小项目翻出来写写.由于之前对go爬虫的一些基础知识不熟悉, ...
- 使用 PyRsa 库解决新版正方教务的 RSA 加密问题并模拟登陆的 Python 实现
前言 先前有一个契机,需要模拟登陆学校所使用的正方教务来获取课程表,其所使用的 RSA 加密方法没有现成的 Python 库可使用,目前其他的 CSDN 博客所采用的方法均无法成功登陆,故只能自己研究 ...
- 新版正方教务网模拟登录python实现
新版正方教务网模拟登录 前言 正好大学期末考完,分析一下正方教务网登录,实现模拟登录就可以自己实现抢课和查询成绩等常规操作了 申明 本文以我学校的正方教务网为例,关键信息已打码,理论上适用于所有新版正 ...
最新文章
- Python3多线程threading介绍(转载)
- Caffe在Ubuntu 14.04 64bit 下的安装------pycaffe 配置
- Maven最佳实践:版本管理
- SQL SERVER中用户定义标量函数(scalar user defined function)的性能问题
- 计划任务工具cron 的配置和说明
- 每天一个linux命令(56):netstat命令
- 算法代码块总结(持续更新)
- winscp连接windows_winscp登陆云主机,winscp登陆云主机如何登陆,教程详情
- stage3图书管理系统服务器部署
- ajax离开页面方法,jQuery中ajax调用当前页面方法
- 智能优化算法改进算法 -附代码
- SkewTransform
- Hadoop面试题总结(大数据面试)
- C语言LMS双麦克风消噪算法,一种双麦克风语音降噪方法与流程
- Python collections模块之Counter()详解
- Windows10常用功能一键设置优化工具分享
- 随身wifi挑选要注意哪些问题才能避免上当
- 代码之外——名人哲语
- 玩知乎五年,我赚了多少钱?
- 输入圆半径计算圆周长、圆面积、圆球表面积,结果保留3位小数。 注意:输入的半径可能是整数,也可能是小数。