起因

逛 Github 看到 南大家园 的项目,

就想着 诶 我们学校官网 能不能 扒出来什么好玩的 深入分析学习 一下网站(确信)

说干就干,第一步当然是实现 模拟登录


于是就有了这个:

Github 项目地址: dogxii/zzuli-node-login

基于 Nodejs 实现了 登录二维码生成,获取钥匙(JSESSIONID)去访问一些需要用户认证的接口。

分析登录

  1. 第一步当然是访问 官网 的登录页面进行 Cookie 分析

    登录页面 右键进入检查,点开 应用 - Cookie

    cookie

    可以看到 Cookie 这里设置了 client 和 JSESSIONID,path 为 /cas/,

    这时候就可以基本判断为 Java 实现的 CAS 单点登录 了。

    (不过我当时并没有判断出来,因为我根本不知道 CAS 是什么,经过后面的分析才得知 QAQ


下面步骤省略 n 张图…

  1. 打开 网络 可以看到有持续的 casSweepCodeLoginQuery 的请求,用于判断登录二维码状态

  2. 元素 页面找到 刷新二维码 标签,看到标签的点击事件绑定了 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)
    }
    1. 由上面代码分析出来二维码生成流程:生成 随机 uuid,用 qrcode() 生成对应 https://kys.zzuli.edu.cn/cas/openAuth?uuid=xxxx 链接的二维码。

    2. 一步一步找相关代码,复刻二维码 轮询

    /**
    * 生成uuid
    */
    function getUuid() {
    // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
    return 'xxxx4xxxyxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
    v = c == 'x' ? r : (r & 0x3) | 0x8
    return 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') {
    // 每次重置token
    token = json.obj.token
    }
    // 等待授权
    else if (json.success && json.obj.code == 'waitAuthorized') {
    // 每次重置token
    token = json.obj.token
    // 增加已扫码效果
    $('.successTishi').show()
    $('#qrLogin').hide()
    $('#qrLogin').empty()
    }
    // 已授权
    else if (json.success && json.obj.code == 'alreadyAuthorized') {
    // 刷新页面并跳转至service
    clearInterval(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/pcLogin
    Response: 302 Found
    Location: https://campus.zzuli.edu.cn/portal-pc/login/pcLogin?ticket=ST-108096-xxxxx-jinghua
    1. 携带 ticket 请求后再次 302 重定向 到首页,获取到最终 JSESSIONID
    GET https://campus.zzuli.edu.cn/portal-pc/login/pcLogin?ticket=ST-108096-xxxxx-jinghua
    Response: 302 Found
    Set-Cookie: JSESSIONID=13113A3D836F74E055B66A92925D8343; Path=/portal-pc; HttpOnly
    Location: https://campus.zzuli.edu.cn/portal-pc/login/pcLogin

    好像还有 CASTGC 等凭证?文章隔了很长时间再写 有些忘记了 =x=

访问 API

这里只提供一个获取学生信息的 API(QAQ),

携带 JSESSIONID 发送 GET 请求

GET https://kys.zzuli.edu.cn/authentication/member/getUserInfo
Cookie: 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