用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)

前言

最近做一个微信小程序,需要登录教务系统。提前用python尝试一下登录接口,并获取到课表打印出来。

我们学校用到新版正方教务系统,长这个样子。

用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第1张图片
相比旧版的教务系统,唯一好处是不用输入二维码方便爬虫登录。但登录时用到RSA加密密码发送请求。


正文

分析网页:
在网页上填上随便写的账号密码,点击登录。开发者工具记录如下:

用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第2张图片

首先它点击登录后,提交一个表单,Form Data一共有4个数据

提交的数据 解释
csrftoken 为了防止跨站域请求伪造 。在登录页源码里有,每次刷新都会变更
yhm 输入的用户名
mm 输入的密码,被加密过。我们主要关注这一个加密过程

csrftoken 如图所示:
用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第3张图片

另外,在开发者工具里,我们还需要注意服务器的一条get请求
用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第4张图片

查看它返回的数据
用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第5张图片
这条Get请求作用:发送当前时间戳,返回公共密钥。并且每次modulus的数据不同,有人会问,这个东西干嘛的?
其实,这是在密码加密时用到的公钥,如果你也曾看过它的密码加密代码,就会了解到。
我们翻一下网站的Javascript,看看密码加密过程

	$.getJSON(_path+"/xtgl/login_getPublicKey.html?time="+new Date().getTime(),function(data){
		modulus = data["modulus"];
		exponent = data["exponent"];
	});
		if($("#mmsfjm").val() == '0'){
			$("#hidMm").val($("#mm").val());
		}else{
			var rsaKey = new RSAKey();
			rsaKey.setPublic(b64tohex(modulus), b64tohex(exponent));
			var enPassword = hex2b64(rsaKey.encrypt($("#mm").val()));
			$("#mm").val(enPassword);
			$("#hidMm").val(enPassword);   
		}

具体加密过程:首先获取modulus,exponent。将他们从base64转16进制,再通过RSA算法生成公钥。
用公钥将密码生成私钥从16进制转回base64。变成最终加密的密码。

至此,我们分析完成,在python里面要做的步骤:
1、获取到csrftoken
2、发送时间戳获取到PublicKey
3、生成RSA加密的密码
4、POST请求登录


python实现过程

登录中唯一难点是生成rsa加密的密码,有大神把js的rsa算法写成python,具体在github里。我已经下载并放在项目文件夹下,直接调用。

头部文件:

import requests
import time
from lxml import etree
from hex2b64 import HB64
import RSAJS

重中之重的RSA加密过程:

    def Get_RSA_Password(self):
        # 生成RSA加密密码
        rsaKey = RSAJS.RSAKey()
        rsaKey.setPublic(HB64().b642hex(self.modulus),HB64().b642hex(self.exponent))
        self.enPassword = HB64().hex2b64(rsaKey.encrypt(self.Password))

与JavaScript原加密算法对比:
用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第6张图片
登录代码:

    def Longin_Home(self):
        # 登录信息门户,成功返回session对象
        self.Get_indexHtml()
        self.Get_csrftoken()
        self.Get_PublicKey()
        self.Get_RSA_Password()
        login_data = [("csrftoken", self.csrftoken),("yhm", self.Username),("mm", self.enPassword),("mm", self.enPassword)]
        login_html = self.session.post(self.login_url + self.now_time,data=login_data)
        # 当提交的表单是正确的,url会跳转到主页,所以此处根据url有没有跳转来判断是否登录成功
        if login_html.url.find("login_slogin.html") == -1: # -1没找到,说明已经跳转到主页
            print("登录成功")
            return self.session
        else:
            print("用户名或密码不正确,登录失败")
            exit()

获取课程表代码

class TimeTable():
    def __init__(self,session,table_url):
        data = {"xnm":2018,"xqm":12}
        table_info = session.post(table_url,data = data).json()
        for each in table_info["kbList"]:
            plt = r'{} | {:<8s} | {:<13s} | {:<15s} | {:<22s}'
            print(plt.format(each["xqjmc"], each["jc"], each["cdmc"], each["zcd"], each["kcmc"]))

登录成功后已经可以为所欲为了,获取课程表只是一个简单操作,就没怎么优化代码了。
用Python登陆新版正方教务系统获取课程表(及RSA加密密码实现)_第7张图片
完整代码:

# -*- coding=utf-8 -*-
import requests
import time
from lxml import etree
from hex2b64 import HB64
import RSAJS

class Longin():

    def __init__(self,user,password,login_url,login_KeyUrl):
        # 初始化程序数据
        self.Username = user
        self.Password = password
        nowTime = lambda:str(round(time.time()*1000))
        self.now_time = nowTime()

        self.login_url = login_url
        self.login_Key = login_KeyUrl

    def Get_indexHtml(self):
        # 获取教务系统网站
        self.session = requests.Session()
        self.session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "max-age=0",
    "Connection": "keep-alive",
    "Referer": self.login_url+ self.now_time,
    "Upgrade-Insecure-Requests": "1"
})
        self.response = self.session.get(self.login_url+ self.now_time).content.decode("utf-8")

    def Get_csrftoken(self):
        # 获取到csrftoken
        lxml = etree.HTML(self.response)
        self.csrftoken = lxml.xpath("//input[@id='csrftoken']/@value")[0]

    def Get_PublicKey(self):
        # 获取到加密公钥
        key_html = self.session.get(self.login_Key + self.now_time)
        key_data = key_html.json()
        self.modulus = key_data["modulus"]
        self.exponent = key_data["exponent"]

    def Get_RSA_Password(self):
        # 生成RSA加密密码
        rsaKey = RSAJS.RSAKey()
        rsaKey.setPublic(HB64().b642hex(self.modulus),HB64().b642hex(self.exponent))
        self.enPassword = HB64().hex2b64(rsaKey.encrypt(self.Password))

    def Longin_Home(self):
        # 登录信息门户,成功返回session对象
        self.Get_indexHtml()
        self.Get_csrftoken()
        self.Get_PublicKey()
        self.Get_RSA_Password()
        login_data = [("csrftoken", self.csrftoken),("yhm", self.Username),("mm", self.enPassword),("mm", self.enPassword)]
        login_html = self.session.post(self.login_url + self.now_time,data=login_data)
        # 当提交的表单是正确的,url会跳转到主页,所以此处根据url有没有跳转来判断是否登录成功
        if login_html.url.find("login_slogin.html") == -1: # -1没找到,说明已经跳转到主页
            print("登录成功")
            return self.session
        else:
            print("用户名或密码不正确,登录失败")
            exit()

class TimeTable():
    def __init__(self,session,table_url):
        data = {"xnm":2018,"xqm":12}
        table_info = session.post(table_url,data = data).json()
        for each in table_info["kbList"]:
            plt = r'{} | {:<8s} | {:<13s} | {:<15s} | {:<22s}'
            print(plt.format(each["xqjmc"], each["jc"], each["cdmc"], each["zcd"], each["kcmc"]))

if __name__ == "__main__":
	# 登录主页url
    login_url = "http://学校主页/jwglxt/xtgl/login_slogin.html?language=zh_CN&_t="
    # 请求PublicKey的URL
    login_KeyUrl = "http://学校主页/jwglxt/xtgl/login_getPublicKey.html?time="
	# 登录后的课表URL
    table_url = "http://学校主页/jwglxt/kbcx/xskbcx_cxXsKb.html?gnmkdm=N2151"

    zspt = Longin("输入你的账号","输入你的密码",login_url,login_KeyUrl)
    response_cookies = zspt.Longin_Home()
    table = TimeTable(response_cookies,table_url)



最后需要注意的地方:
为了代码更有移植性,方便各位在自己的学校的教务系统登录,整个代码几乎封装好了。也即是说,只要你的学校也使用新版正方教务系统,那么稍微阅读代码(特别是if "__name__"==__main__:这部分),改三个网站地址便可调用。

程序里调用的hex2b64、RSAJS库和完整代码我已经打包好了,上传到csdn里。或者你也可以在评论留下邮箱,看到了就发给你。



CSDN下载链接:

https://download.csdn.net/download/koevas/11010479

百度云:

链接:https://pan.baidu.com/s/1gkMXCzXdx5NWUUs8bvGM-A
提取码:it2x



参考资料:

https://github.com/Pusnow/pyjsbn-rsa
https://blog.csdn.net/qq_33278884/article/details/80936714
https://www.v2ex.com/t/433971





你可能感兴趣的:(Python)