郑大一卡通鉴权分析笔记【一】

本文记录一次对郑州大学 豫见郑大 App 进行电费接口分析的过程。
主要记录分析思路和踩坑过程。
自己分析过程中打的笔记有点乱,由 AI 进行了 Markdown 排版


0. 为什么要这么做

今天是去郑州大学报道的第一天,上午报道后,宿管说电费的查询与交费都在 豫见郑大 App 上完成。

我马上就想到,要是我们都没检查电费,万一哪天:

  • 打着游戏宿舍电路欠费停电了,电脑帧率瞬间被强制降低
  • 快睡着时空调电路欠费停电了,被热起来交电费重新开空调

不愉快的事情还能例举很多……

如果可以定时检查电费,并且在电费不够时提示我,就不会出现这样的顾虑了。

此外,如果可以一直记录电费,长期以来就可以看出电费趋势了!(虽然没什么用)


1. 获取电表数据

最开始的目标非常简单:

获取豫见郑大App里面一卡通平台的电表数据。

我想直接把 App 扔进 MuMu 模拟器(可以 Root)里,然后用 Reqable 抓包分析。

结果第一步就寄了。

1.1 模拟器抓包并不行

把 App 放进 MuMu 模拟器运行,logcat 里报错:

1
Entry not found

这时候第一反应是:

  • App 有模拟器检测?【也许,因为有壳】
  • 加壳导致的?【确实有壳】
  • 或者是不是 ABI 不支持?【多半是,毕竟 Entry 多半只能指 so 文件里的 Entry 了吧】

总之,正常运行并抓包这条路暂时走不通。

于是换一种思路。

1.2 从分享链接入手

在电费页面里发现了一个比较有意思的功能:

分享 → 微信

通过这个功能拿到了分享 URL。

打开这个 URL 并点击“继续访问”,可以进入登录页面。

然而登录后,并没有进入电费页面,服务器 502 了

不过,至少有一个非常重要的收获:

分享页面跳转的目标网址拿到了。

尝试直接访问这个目标网址。

1
https://ecard.v.zzu.edu.cn/server/auth/host/open?host=11&org=2

结果显示无法获取“树维认证参数”

【顺带一提“树维”是一家科技公司,豫见郑大 App 的包名中 “supwisdom” 就是他们】

看来还是绕不开鉴权。


2. 从 WebView 突破

这时候突然发现:

一卡通页面看起来特别像一个网页!

于是打开了开发者模式的 显示布局边界:

一个大大的输出窗口,四个小蓝角,加一个大叉,基本可以确认这里是 WebView。

既然是 WebView,那就好办了。

尝试使用 Chrome 的 chrome://inspect 连接 Android 设备。

成功连上 WebView!

分析难度瞬间降低了!

在 DevTools 里看网络请求即可!

【题外话:App 首页也是一个 Webview,但是 chrome://inspect 里无法发现这个 Webview,我不知道为什么】


3. 找到电费接口

打开一卡通页面,进入电费查询功能。

发现了目标接口:

1
https://ecard.v.zzu.edu.cn/server/utilities/account

请求里带上对应的宿舍信息,接口返回个人信息、账户余额、电费余额、计价标准等等

到这里,目标接口已经基本确定。

但是还有一个问题:

鉴权


4. 踩坑:奇怪的 500

首先,我先把整个请求原封不动复制到 Reqable 里,手动发送。

结果服务器返回了 500

???

明明浏览器里可以正常请求,复制出来却 500?

我从请求头请求体一直排查,甚至怀疑有请求重放攻击拦截,搞了我半天.

最后发现一个非常离谱的问题:

如果 JSON 里只要出现空格或者换行,服务器就会直接返回 500。

例如原本:

1
{"utilityType":"electric","bigArea":"","area":"99"}

请求时我用了格式化 JSON 功能,大概变成这样:

1
2
3
4
5
{
"utilityType": "electric",
"bigArea": "",
"area": "99"
}

服务器就会 500

man what can i say

什么奇奇怪怪的后端实现!

感觉像是学长们自己写的 JSON 解析(


5. Authorization:有点像 JWT 的鉴权

我尝试逐项删除请求头。

最后发现最重要的鉴权信息是:

1
Authorization: xxx.xxx.xxx

这种格式第一眼就非常像 JWT 呀!

【很可惜,并不是标准 JWT,解析不出】

请求里还有另外一个:

1
X-Id-Token: xxx.xxx.xxx

不过把这个请求头删掉之后,接口依然可以正常访问。

所以暂时认为 X-Id-Token 应该不是电费接口当前鉴权所必需的。

我开始找 Authorization 的内容是从哪里来的。


6. 可以续杯的 Token

观察这个 Token 的生命周期。

一个很有意思的现象出现了:

如果不继续访问接口,一小时内这个 Token 就会过期。

如果持续访问接口,例如每 10 分钟访问一次,Token 好像就一直不会过期。

应该是服务器那边刷新了 Token 时效。

理论上可以做一个定时任务,每 10 分钟请求一次接口,就可以保持 Token 一直有效,一直获取电量了。

这样还可以每天自动记录宿舍的用电量曲线,一举两得。

我还沾沾自喜呢,简单。


7. 什么神经的维护时间

我突然就瞟到电费页面上写着 23:40 ~ 01:00 不开放

每天维护一小时干嘛!

当然,合理的解释很多,比如数据库维护、业务系统更新、定时任务等。

但在当时的我看来,多半就是为了防我这种人的。

那我就得看看网页在 Token 过期时干了什么了。

我等过了一小时,再刷新页面。

首先网页尝试访问:

1
/server/utilities/config

返回了 401 Unauthorized。

紧接着就访问了新的接口!

1
/server/auth/updateToken

随后页面自动刷新。

刷新完成后又可以正常用了。

这说明 Token 过期以后,前端会自动进行 Token 更新。


8. updateToken:Token 还是能续杯

进一步观察 /server/auth/updateToken 接口。

发现这个接口的请求中,会携带旧的 Authorization 里的 Token。

服务器返回了一个新的 accessToken。

然后把这个新的 Token 当作下一次请求的 Authorization 就可以继续正常访问了。

接下来就好办了:

拿到一个 accessToken 放入 Authorization 中请求页面。

如果 accessToken 过期了,就用 /server/auth/updateToken 接口去换新的 accessToken。

已经可以实现定时获取电量了!


9. 双 Token 傍地走,安能辨我是雌雄

9.1 两个 Token 有什么区别

/server/auth/updateToken 接口里返回的不止有 accessToken,还有一个 updateToken。

我看到 localStorage 里也同时存了

1
2
accessToken:  xxxx....hss
refreshToken: xxxx....QUA

于是我突发奇想:

如果我把 accessToken 删掉,网页会怎么样?

直接删掉 LocalStorage 里的 accessToken。

结果网页很快就发现 accessToken 不见了

然后自动调用 /server/auth/updateToken 接口

而这一次,它传进去的并不是原来的 accessToken,而是:

1
refreshToken: xxxx....QUA

然后成功拿到了新的 Token。

到这里还只能说明 refreshToken 确实可以拿来刷新 Token。

【其实我觉得 refreshToken 就应该被拿来刷新 accessToken】

但接下来发现的东西就有点离谱了。

9.2 两个 Token 似乎没什么区别

继续测试发现:

accessToken 和 refreshToken 居然都可以直接用于鉴权。

而且,如果调用 updateToken 接口,无论传入:

1
accessToken

还是:

1
refreshToken

都可以正常得到新的:

1
2
accessToken
refreshToken

于是目前观察到的行为可以概括成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                  ┌── accessToken ──┐
│ │
任意一个有效 Token ┤ ├──→ 鉴权
│ │
└─ refreshToken ──┘

任意一个有效 Token
│
▼
updateToken 接口
│
▼
新的 accessToken
新的 refreshToken

所以我得出了一个相当离谱的结论:

目前来说,accessToken 和 refreshToken 相当于双保险,两者地位完全相同。

当然,严格来说,这并不能证明后端设计上两者真的完全等价,只能说明:

至少在目前抓到的这些接口和测试场景中,服务器并没有严格区分它们。

9.3 验证我的猜想

既然如此,那就干脆来个更暴力的测试。

原本:

1
2
accessToken:  xxxx....hss
refreshToken: xxxx....QUA

交换:

1
2
accessToken:  xxxx....QUA
refreshToken: xxxx....hss

然后重新进入网页。

结果:

正常运行。

???

man what can i say

什么奇奇怪怪的后端实现!

至少从目前的实验结果来看,accessToken ≈ refreshToken。


郑大一卡通鉴权分析笔记【一】
http://example.com/2026/09/04/郑大一卡通鉴权分析笔记【一】/
作者
TRIM5829
发布于
2026年9月4日
许可协议