起因
逛 Github 看到 南大家园 的项目,
就想着 诶 我们学校官网 能不能 扒出来什么好玩的深入分析学习 一下网站(确信)
说干就干,第一步当然是实现 模拟登录 了
于是就有了这个:
Github 项目地址: dogxii/zzuli-node-login
基于 Nodejs 实现了 登录二维码生成,获取钥匙(JSESSIONID)去访问一些需要用户认证的接口。
分析登录
-
第一步当然是访问 官网 的登录页面进行
Cookie 分析了在 登录页面 右键进入检查,点开 应用 - Cookie,

可以看到 Cookie 这里设置了 client 和 JSESSIONID,path 为 /cas/,
这时候就可以基本判断为 Java 实现的
CAS 单点登录了。(不过我当时并没有判断出来,因为我根本不知道 CAS 是什么,经过后面的分析才得知 QAQ)
下面步骤省略 n 张图…
-
打开
网络可以看到有持续的casSweepCodeLoginQuery的请求,用于判断登录二维码状态 -
在
元素页面找到 刷新二维码 标签,看到标签的点击事件绑定了 createQrCode() 函数:<span onclick="createQrCode()" ...>刷新二维码</span>在
控制台输入 createQrCode 获取到定义,点击找到相应的 js 位置(可以看到是直接写入页面 scirpt 中的/*** 生成二维码*/function createQrCode() {var uuid = getUuid()clearInterval(num)token = ''$('#qrLogin').empty()$('#qrLogin').qrcode({render: 'canvas',width: 200,height: 200,text: location.origin + '/cas/openAuth?uuid=' + uuid,})$('#qrLogin').show()$('.successTishi').hide()$('.ewmModal').hide()// TODO 启动获取状态 2分钟内每秒执行一次getAuthorizationJob(uuid)}-
由上面代码分析出来二维码生成流程:生成
随机 uuid,用qrcode()生成对应 https://kys.zzuli.edu.cn/cas/openAuth?uuid=xxxx 链接的二维码。 -
一步一步找相关代码,复刻二维码
轮询:
/*** 生成uuid*/function getUuid() {// xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxxreturn 'xxxx4xxxyxxxxxxx'.replace(/[xy]/g, function (c) {var r = (Math.random() * 16) | 0,v = c == 'x' ? r : (r & 0x3) | 0x8return v.toString(16)})}/*** 获取认证信息*/function getAuthorization(uuid) {$.ajax({url: 'casSweepCodeLoginQueryController',type: 'POST',data: {uuid: uuid,token: token,},success: function (jsonObj) {var json = JSON.parse(jsonObj)// 等待扫码if (json.success && json.obj.code == 'waitSweep') {// 每次重置tokentoken = json.obj.token}// 等待授权else if (json.success && json.obj.code == 'waitAuthorized') {// 每次重置tokentoken = json.obj.token// 增加已扫码效果$('.successTishi').show()$('#qrLogin').hide()$('#qrLogin').empty()}// 已授权else if (json.success && json.obj.code == 'alreadyAuthorized') {// 刷新页面并跳转至serviceclearInterval(num)if ('' == '' || $.trim('').length <= 0) {window.location.href = location.origin + '/cas/login'} else {window.location.href = location.origin + '/cas/login?service=' + ''}}// 已取消else if (json.success && json.obj.code == 'cancel') {// 增加遮罩层二维码失效clearInterval(num)$('.ewmModal p').text('授权已取消')$('.ewmModal').show()}// 已失效else if (json.success && json.obj.code == 'invalid') {// 增加遮罩层二维码失效clearInterval(num)$('.ewmModal p').text('授权已失效')$('.ewmModal').show()}// 不合法else if (json.success && json.obj.code == 'error') {// 增加遮罩层二维码失效clearInterval(num)$('.ewmModal p').text('授权已失效')$('.ewmModal').show()}},error: function (msg) {// 增加遮罩层二维码失效clearInterval(num)$('.ewmModal p').text('授权已失效')$('.ewmModal').show()},})}// ... ...具体过程不阐述。6. 二维码轮询返回
alreadyAuthorized后,跳转到 CAS 登录验证界面 https://kys.zzuli.edu.cn/cas/login?service=xxxx ,这里service为需授权域名登录地址,请求后302 重定向地址并附带ticke参数:GET https://kys.zzuli.edu.cn/cas/login?service=https://campus.zzuli.edu.cn/portal-pc/login/pcLoginResponse: 302 FoundLocation: https://campus.zzuli.edu.cn/portal-pc/login/pcLogin?ticket=ST-108096-xxxxx-jinghua- 携带
ticket请求后再次302 重定向到首页,获取到最终JSESSIONID:
GET https://campus.zzuli.edu.cn/portal-pc/login/pcLogin?ticket=ST-108096-xxxxx-jinghuaResponse: 302 FoundSet-Cookie: JSESSIONID=13113A3D836F74E055B66A92925D8343; Path=/portal-pc; HttpOnlyLocation: https://campus.zzuli.edu.cn/portal-pc/login/pcLogin好像还有 CASTGC 等凭证?文章隔了很长时间再写 有些忘记了 =x=
-
访问 API
这里只提供一个获取学生信息的 API(QAQ),
携带 JSESSIONID 发送 GET 请求
GET https://kys.zzuli.edu.cn/authentication/member/getUserInfoCookie: JSESSIONID=77EF782EAD98CD505AE9E8FCA480A81A返回数据:
{ "success": true, "msg": "操作成功", "obj": { "memberId": "542500010101", "memberUsername": "542500010101", "memberPwd": null, "memberNickname": "奶龙", "memberSex": 1, "memberPhone": "114****14", "memberIdNumber": "419****1111", "memberCreateTime": 1725420135000, "memberState": 1, "memberAcademicNumber": "542500010101", "memberMailbox": "54****0101@email.zzuli.edu.cn", "memberSign": "1", "memberImage": null, "memberOtherSchoolNumber": "542500010101", "memberOtherNation": "龙族", "memberOtherDepartment": "奶龙学院", "memberOtherMajor": "奶龙技术", "memberOtherGrade": "2025", "memberOtherClass": "奶龙班25-01", "memberOtherBirthday": "2008-08-08", "memberOtherNative": null, "lastLoginTime": "2025年05月28日 22:17", "quicklyTicket": null, "roleCodeList": ["student"], "roleList": [ { "roleCode": "student", "roleName": "学生", "roleState": null, "roleComment": null } ], "deptList": [ { "dptCode": "000210", "dptName": "奶龙学院", "dptAbbreviation": null, "dptCategoryCode": null, "dptEngName": null, "dptBelong": null, "dptLevel": null, "dptSetOtherCode": null, "dptSetUpYear": null, "dptOriginalStandardCode": null, "dptStartTime": null, "dptEndTime": null, "dptSort": null, "dptState": null, "dptPublishState": null, "children": null, "memberList": null, "title": "奶龙学院", "name": "奶龙学院" } ], "deptCodeList": ["000210"], "memberIdAesEncrypt": "wxnSLWezwtresLZSfA%3D%3D", "memberAesEncrypt": "tWwL3dawe32csdzZHNKw%3D%3D", "memberCasLastLoginTime": null, "memberAppLastLoginTime": null, "memberPcLastLoginTime": "2025年05月28日 22:17", "memberSalt": null, "memberUpdatePasswordTime": null, "memberIdNumberSign": null }, "attributes": null, "count": null}本文到此结束 =w= 这篇文章好水!
有错误及疑问 欢迎评论区指出
让我们 下期再见(X