zzuli 官网扫码登录分析与模拟

起因

逛 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

评论

加载中...