公司会议室预定系统token逻辑分析

【注意:此文章为博主原创文章!转载需注意,请带原文链接,至少也要是txt格式!】
突发奇想,想写一个抢单位会议室的定时程序,但是手头现在也没什么工具,所以就单纯用浏览器的F12然后分析一波。
随便找一个请求,然后看一下内容如下图:
请求代码如下:
GET /api/mxxxng/front/finxxxPic/13xxxx7 HTTP/2
Host: axxxxxp.cn
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua: "Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"
Sec-Ch-Ua-Mobile: ?0
Awp-Token: eyJ0eXxxidG9rZW4iOiJlMmUzNmMwOS1xxxGb_itI05JrCNLIwOKHvu1rewjg
X-Req-Pdt-Dst: 2
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Token: 4IzQ4fCUyfCoyfDAwMTxxxjeXNoYkxVbGZqNVA3a3NxTHpMc2VLcEpNOxxxHNqTzFDeHZwcFxxxm1GSlVNSDJtNTR6Ly9EbmJZaXlxx
X-Req-Zone-Id: Etc%2FGMT-8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=1, i
Connection: close
每次请求,必然都带Awp-Token
、Token
,主要目的就是分析Awp-Token
、Token
他们值的来源!
第一步 断点追踪
首先根据请求找到对应的请求点,如下图:
注意这里m.send(f || null)
但是到这里,所有的请求都已经准备就绪,包括加密数据都已经完成了,如下图:
所以下一步继续网上翻找,断点上移。这里可能需要一点一点的上移,凭经验,凭细心。因为有的时候加密的数据并不会搜索的到,所以只能不断的翻找,要有充足的耐心。
上移一个断点后,如下图:
往上翻找后发现Awp-Token
、Token
还是已经得到了加密后的值,此刻就要继续向上翻找,一定要找到加密的点,这样才能知道如何加密的。
到这里我们继续向上找函数,如下图,这里我把此处最大化截屏,让大家能尽可能的多看上下文。一定注意其中的l(e)
,如下图:
由此看出跟进到了l
函数里面,但是l
的传参是e
,这个时候的e
函数里面headers
已经存在加密的Awp-Token
、Token
了。不过也相对好追踪了,此刻需要大家注意,上图中箭头的地方,也就是②,这里函数前面有一个数字,是81924
,我们全局搜索一下81924
,看看它在哪里?
这里直接给图下图,同时做一个大概的分析:
如上图,已经标注的很详细了,那么下面就简单了,我们来跟进分析_request这个封装的函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | _request(e, t) { let n, o; "string" == typeof e ? (t = t || {}).url = e : t = e || {}; let {transitional: i, paramsSerializer: s, headers: b} = t = (0, c.Z)(this.defaults, t); void 0 !== i && p.Z.assertOptions(i, { silentJSONParsing: u.transitional(u.boolean), forcedJSONParsing: u.transitional(u.boolean), clarifyTimeoutError: u.transitional(u.boolean) }, !1), null != s && (r.Z.isFunction(s) ? t.paramsSerializer = { serialize: s } : p.Z.assertOptions(s, { encode: u.function, serialize: u.function }, !0)), t.method = (t.method || this.defaults.method || "get").toLowerCase(); let d = b && r.Z.merge(b.common, b[t.method]); b && r.Z.forEach(["delete", "get", "head", "post", "put", "patch", "common"], e => { delete b[e] } ), t.headers = l.Z.concat(d, b); let M = [] , f = !0; this.interceptors.request.forEach(function(e) { if ("function" != typeof e.runWhen || !1 !== e.runWhen(t)) f = f && e.synchronous, M.unshift(e.fulfilled, e.rejected) }); let z = []; this.interceptors.response.forEach(function(e) { z.push(e.fulfilled, e.rejected) }); let O = 0; if (!f) { let e = [a.Z.bind(this), void 0]; for (e.unshift.apply(e, M), e.push.apply(e, z), o = e.length, n = Promise.resolve(t); O < o; ) n = n.then(e[O++], e[O++]); return n } o = M.length; let h = t; for (O = 0; O < o; ) { let e = M[O++] , t = M[O++]; try { h = e(h) } catch (e) { t.call(this, e); break } } try { n = a.Z.call(this, h) } catch (e) { return Promise.reject(e) } for (O = 0, o = z.length; O < o; ) n = n.then(z[O++], z[O++]); return n } |
其实这里没用的代码特别多。我们只需要分析重点即可。这里可以通过一步一步的调试,找到重点部分即可。
这里通过在 let n, o;
也就是函数最开始的地方就断点,然后一步一步走,然后其实上面的代码虽然长,但是核心代码如下图:
图中才是封装head头的整体部分。这里肯定就会涉及Awp-Token
、Token
。其实最核心的代码就是:
if (!f) {
let e = [a.Z.bind(this), void 0];
for (e.unshift.apply(e, M),
e.push.apply(e, z),
o = e.length,
n = Promise.resolve(t); O < o; ) //最核心的组装head头的代码在这里
n = n.then(e[O++], e[O++]); //在这里进行组装,要跟到这里面去!
return n
}
那我们就单步跟进去看看,这里到底是怎么做的。
跟进去后如下图:
其实到这里就非常明显了。我们只需要了解(0,t.tn)()
、(0,t.vC)()
这两个函数就OK了。这里t.tn
返回的是M
函数,t.vC
返回的是h
函数然后直接跟进去,如下图:
到这里就非常非常清晰了。
Awp-Token = sessionStorage.getItem("Awp-Token")
Token = sessionStorage.getItem("Ulp-Token")
这里有个知识点:sessionStorage
的存储范围是 当前的浏览器选项卡。每个选项卡都有自己独立的 sessionStorage
空间,即使是同一个页面在多个选项卡打开,它们的 sessionStorage
并不共享。
当选项卡或窗口关闭时,sessionStorage
会自动清空,不会保留数据。
这里就延申另一个问题了,既然这个值已经在标签的会话存储中了,那么到底是哪来的值???
那下一步我们删除这两个值,然后刷新页面看看。哪个请求会返回这两个值,或者说哪个请求写入了这两个值,具体是怎么写入的?
刷新页面后,具体请求链路如下:
第一个user-info请求已经提示“请重新登录”了。那么我们看第二个。
因为公司有统一登录系统,统一登录系统SSO是把token写在cookie中的。所以直接请求sso是会携带sso自己的cookie的,此处这里没有做详细分析,但是在最下面有个简短的分析①。如下图:
因中间的调用环节异常复杂这里就不深入分析如何调用https://uxxx.sso.cn/api/web/auth/login_by_sso 简单点说就是{"code":2300,"msg":"请重新登录","info":null}
然后程序根据2300
,然后调用sso重新登录。传参是url=https%3A%2F%2Fhys.xxxx.cn&env=prod
,只要sso请求head中cookie的token没有时效,那么页面会返回:
1 2 3 4 5 6 7 8 | <noscript> <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed. </noscript> |
看代码也明白了,下一步页面会自动加载https://sso.xxxxx.cn/web/auth/sso_redirect
,提交的参数也写的很明确,就是拿url
、env
、token
请求这个地址。因为请求包很明确,这就不看了,我们注意这个请求的返回包如下图:
从上面的返回包明显可以看出,直接302跳转到https://sso.xxxx.cn/ulp-esso?redirect_param=base64-xxxx-xxx%3D
这里解码这个redirect_param参数的base64值结果,然后完整的是https://sso.xxxx.cn/ulp-esso?redirect_param=https://hys.xxxx.cn/mms/?sysBranchName=AWP_MS&targetpath=cardCode&token=6CCxxxxxxxxx37
到这里基本就清晰了。然后我们可以看一下这个请求返回的是什么,如下图:
到这里其实也很清晰,看html就是按redirect_param中的url地址,带着token再跳过去。如下图:
然后系统再把这个token、ulpToken写入浏览器缓存就OK了。至此整个流程就算分析完毕。下面整理一个简单的图来概括一下。
后续写抢会议室的脚本也简单了,就是按上面的逻辑写python掉接口就OK。
===================
额外章节
===================
①:这里对如何加载sso登录 https://sso.com/api/web/auth/login_by_sso 做一个简单的说明。
其实就是通过iframe然后传参并加载这个页面。
第一步:接口返回2300,然后如下图
第二步:返回到了之前说的异步request请求里面
第三步:从这个异步请求出去后,因为token过期了,所以进入过期请求处理函数
第四步:过期后,系统自动进入startSSO()函数,重新获取SSO登录态
第五步:通过SSO函数,最终创建iframe里面。如下图
第六步:创建并加载iframe页面,并加载页面。具体JS逻辑核心部分如下图(下面的图片特别大,所有图片都可以点击放大)
具体加载了什么样式的页面,如下图:
布施恩德可便相知重
微信扫一扫打赏
支付宝扫一扫打赏