Skip to content

文章来源: 52pojie
本人是作者

心里测评网站登录接口分析

前言

最近我校突然让我们制作心理测评网站,然后登录中发现有滑块验证码等操作(之前都是直接登录无需验证码,现在界面变的很整洁以及引入了验证码登录) 本人目前初二(八年级)第一次写文章,所以文章可能会有些地方写的不清楚,请谅解! 网站:aHR0cHM6Ly96eXpmeC5wc3l5dW4uY29tLw==

开始分析

验证码获取

验证流程(失败处理): PixPin_2025-09-27_14-34-28.png base64图像示例: originalImageBase64: PixPin_2025-09-27_14-22-17.png jigsawImageBase64: PixPin_2025-09-27_14-22-23.png 可以看到是滑块验证码 这个是比较好解的(ddddocr可以解决) 重点是在请求 接下来是验证成功的请求流程: PixPin_2025-09-27_14-20-28.png

解析: 获取验证码:

Get https://zyzfx.psyyun.com/code?userName=<UserName>
// UserName为用户名
Headers:
Authorization: Basic xxx //Auth必备条件
TENANT-ID: 440 // 租户ID(相当于学校ID)

PixPin_2025-09-27_14-21-46.png Authorization固定的值: PixPin_2025-09-27_14-50-19.png TENANT-ID生成方法:

Get https://zyzfx.psyyun.com/admin/tenant/detailByCode?code=<your school code>
// 其中code为学校的代码(网址前缀)

Result: PixPin_2025-09-27_15-04-05.png

很好,关键的一些请求头都获取到了 接下来是编写Python代码(获取验证码):

import requests

# Disable SSL Verification
requests.packages.urllib3.disable_warnings()
# Init
session = requests.Session()
session.verify = False
session.headers = {
  "Authorization": "Basic xxx", // 获取到的Authorization(可直接写进里面)
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0",
  "Accept": "application/json, text/plain, */*",
  "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", // 默认请求方式
  "TENANT-ID": "440", // 租户ID
}
UserName = "xxx" // 用户名
captcha = session.get(f"https://zyzfx.psyyun.com/code?userName={UserName}&captchaType=blockPuzzle").json() // 获取验证码
imageOriginalBase64 = captcha["data"]["repData"]["originalImageBase64"] // 背景图
imageOriginal = base64.b64decode(imageOriginalBase64)
imagePuzzleBase64 = captcha["data"]["repData"]["jigsawImageBase64"] // 拼图
imagePuzzle = base64.b64decode(imagePuzzleBase64)
token = captcha["data"]["repData"]["token"] // token
secretKey = captcha["data"]["repData"]["secretKey"] // 安全密钥

验证码验证流程

先分析请求 PixPin_2025-09-27_15-17-15.png

Post请求,内容为空 Query String: userName: 1 // 用户名 captchaType: blockPuzzle // 验证码类型:滑块验证码 pointJson: c+NDG2sxh8ntCz9nlBvmf3/0T4Z/I1PwkvcC9D2KBlg= // 加密内容 secretKey:aGvrWRMPxKoeuGMe token: c62b362137b542fcab00ff073a1932a1 // code中获取到的token

js分析: PixPin_2025-09-27_15-28-01.pngPixPin_2025-09-27_15-32-46.png

解析(pointJson) 使用AES中的ECB模式加密,填充方式为Pkcs7方式,secretKey为加密密钥 在o.a函数中,t为key(获取arguments,获取第二项作为key密钥(没有默认使用密钥:XwKsGlMcdPMEhR1B))

返回内容分析: 失败:

{
    "code": 0,
    "msg": "成功",
    "data": {
        "repCode": "6111",
        "repMsg": "验证失败",
        "repData": null,
        "success": false  // 验证失败的判断
    },
    "success": true
}

成功:

{
    "code": 0,
    "msg": "成功",
    "data": {
        "repCode": "0000",
        "repMsg": null,
        "repData": {
            "captchaId": null,
            "projectCode": null,
            "captchaType": "clickWord",
            "captchaOriginalPath": null,
            "captchaFontType": null,
            "captchaFontSize": null,
            "secretKey": null,
            "originalImageBase64": null,
            "point": null,
            "jigsawImageBase64": null,
            "wordList": null,
            "pointList": null,
            "pointJson": "c+NDG2sxh8ntCz9nlBvmf3/0T4Z/I1PwkvcC9D2KBlg=", // 请求里面的pointJson
            "token": "c62b362137b542fcab00ff073a1932a1", // token值
            "result": true,
            "captchaVerification": null,
            "clientUid": null,
            "ts": null,
            "browserInfo": null
        },
        "success": true // 验证成功的判断
    },
    "success": true
}

这个请求更像是在验证是否正确滑到指定位置(无其他返回参数)

登录部分

PixPin_2025-09-27_15-44-58.png

Query String: randomStr: blockPuzzle // 默认滑块验证码 code: grILnYI7HmW41fTsrP7O/WrHm3qTRbHLt0Rq1KVrvLzdfKCSGGKWwGLnuGB6OHfqN5/1jFY457Zrz+LFL0MGG3RE5ka9Y04xQKpe06cmkOs= // 特殊加密的code,其里面包含了获取验证码的token与PointJson的内容 grant_type: password // 粗略推测应该是使用密码类型登录 username: 1 //用户名

POST内容(表单数据): username: 1 // 用户名 password: Hgn8K/2pda3c5rEnoZIcpA== // 特殊加密的密码

Headers: isToken: false // 默认为false TENANT-ID: 440 // 前文提到的学校ID Authorization: Bearer xxx // 前文提到的Authorization验证部分 Content-Type: application/x-www-form-urlencoded;charset=utf-8 // 默认请求内容的方式:form格式

js代码分析: PixPin_2025-09-27_15-59-02.pngPixPin_2025-09-27_16-10-22.png 先看到key值的部分: PixPin_2025-09-27_16-02-17.png

        var i = "r"
          , r = "u"
          , a = "i"
          , o = "g"
          , c = "e";
        function s() {
            return "".concat(i).concat(r).concat(a).concat(o).concat(c).concat(i).concat(r).concat(a).concat(o).concat(c).concat(i).concat(r).concat(a).concat(o).concat(c).concat(c)
                                                // 使用拼接的方式拼凑出key,即为ruigeruigeruigee
        }

PixPin_2025-09-27_16-10-22.png

通过这段代码可得知密码使用的是AES CBC的模式加密(nopadding)且key和iv均为固定密钥:ruigeruigeruigee

接下来是Code部分 我们回到刚刚验证验证码验证成功后的部分 PixPin_2025-09-27_16-17-27.png captchaVerification值是code的证据: PixPin_2025-09-27_16-19-17.png

总结:password使用AES CBC nopadding加密,其iv和key均为ruigeruigeruigee code为AES ECB Pkcs7加密,密钥为secretKey,内容为:

<token>---<pointJson原文>

俩个加密的解决了,接下来是编写python代码逻辑:

def Login(UserName,Password):
    while True:
        # Get Captcha
        session.headers.update({"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"})
        session.headers.update({"Authorization": "Basic xxx"})

        captcha = session.get(f"https://zyzfx.psyyun.com/code?userName={UserName}&captchaType=blockPuzzle").json()
        imageOriginalBase64 = captcha["data"]["repData"]["originalImageBase64"]
        imageOriginal = base64.b64decode(imageOriginalBase64)
        imagePuzzleBase64 = captcha["data"]["repData"]["jigsawImageBase64"]
        imagePuzzle = base64.b64decode(imagePuzzleBase64)
        token = captcha["data"]["repData"]["token"]
        secretKey = captcha["data"]["repData"]["secretKey"]
        # DDDOCR
        res = ocr.slide_match(imagePuzzle, imageOriginal, simple_target=True)
        # Encrypt pointJson
        # AES ECB Encrypt
        originalJsonData = "{" + f"\"x\":{res['target'][0]}.27272727272727,\"y\":5" + "}"
        cipher = AES.new(secretKey.encode(), AES.MODE_ECB)
        encrypted_data = cipher.encrypt(pad(originalJsonData.encode(), AES.block_size))
        encrypted_code = cipher.encrypt(pad((token + "---" +  originalJsonData).encode(), AES.block_size))
        encryptedCodeBase64 = base64.b64encode(encrypted_code).decode().replace(" ","+")
        encrypted = base64.b64encode(encrypted_data).decode()

        encrypted = encrypted.replace(" ", "+")

        # Submit Captcha
        buildPostJson = {
            "userName": UserName,
            "captchaType": "blockPuzzle",
            "pointJson": encrypted,
            "token": token
        }
        #Json To QueryString
        import urllib.parse
        queryString = ""
        for key, value in buildPostJson.items():
            # 对查询参数进行URL编码以避免特殊字符问题
            encoded_value = urllib.parse.quote(str(value), safe='')
            queryString += f"{key}={encoded_value}&"
        queryString = queryString[:-1]
        captchaResult = session.post(f"https://zyzfx.psyyun.com/code/check?{queryString}").json()
        if captchaResult["data"]["success"] == True:
            # # AES CBC Encrypt Password
            encrypted_password_b64 = aes_cbc_zero_padding_encrypt(Password, "ruigeruigeruigee")
            # Login
            # 将URL参数转换为JSON格式再编码为查询字符串
            params = {
                "randomStr": "blockPuzzle",
                "code": encryptedCodeBase64,
                "grant_type": "password",
                "username": UserName
            }
            # 手动构建查询字符串,确保特殊字符被正确编码
            query_string = "&".join([f"{k}={urllib.parse.quote(str(v), safe='')}" for k, v in params.items()])
            url = f"https://zyzfx.psyyun.com/auth/oauth/token?{query_string}"
            session.headers.update({"Content-Type":"application/x-www-form-urlencoded","ignore":"1","origin":"https://zyzfx.psyyun.com"})
            response = session.post(url, data={"username":UserName,"password": encrypted_password_b64})
            loginResult = response.json()
            print(f"登录成功,用户名:{loginResult['user_info']['username']}")
            session.headers.update({"Authorization":f"Bearer {response.json()['access_token']}"})
            break

返回内容: 登录失败:

登录成功: PixPin_2025-09-27_16-32-10.png

其中需要添加请求头Authorization: Bearer access_token内容 添加这个请求头后即可请求接下来的如获取心理测评表等API操作

声明

本项目仅用于学习交流,请勿非法使用。