ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

QQ登录测试实战:OAuth链路拆解与接口自动化全流程

QQ登录测试实战:OAuth链路拆解与接口自动化全流程 简介面向iOS移动应用开发者的QQ第三方登录与分享集成示例项目以QQLoginDemo为主涵盖QQ开放平台接入所需的源代码、工程配置文件与SDK依赖适合学习第三方授权登录的完整流程。压缩包共1985个文件以png图片、xml配置和json数据为主另含jar库、so动态库、apk安装包及aidl接口定义等整体约24.95MBpng多用于界面图标资源xml承担界面布局与权限配置json承载接口数据jar与so则是SDK运行所需依赖目录划分完整便于按模块检索。已有576人学习使用。通过该项目可系统掌握AppID与AppKey申请、SDK导入初始化、Info.plist的URL Scheme回调配置、登录授权解析、用户信息获取与保存以及分享到QQ空间或好友的完整实现链路项目中的示例代码、配置说明和编译产物能帮助开发者快速对照验证减少集成中的常见配置疏漏适合初、中级移动开发者作为第三方登录功能的参考模板。1. QQ登录测试一个登录框为什么值得专门写一篇测试笔记QQ登录几乎是国内第三方应用接入最普遍的账号体系之一用户点一下按钮、授权头像昵称、回跳应用看似几秒的事背后却是 OAuth 授权、Token 换发、登录态持久化、多端互踢、风控拦截一整条链路。很多测试工程师拿到QQ登录测试这个任务时第一反应是不就是点一下登录按钮吗直到上线前被用户反馈登录失败闪退授权后没有回调打脸才发现这个入口的坑比想象中多得多。这篇笔记会从用例设计、自动化落地、数据管理到踩坑排查完整走一遍适合刚接手三方登录测试的工程师也能给做了几年功能测试但没系统性梳理过登录链路的朋友一些参考。文章里所有方案都是我实际落地用过的通用做法不依赖某个特定项目。2. 先拆链路再写用例QQ登录从点按钮到 Login 态全流程测试设计2.1 登录不是一个按钮而是一条六段链路在做 QQ 登录测试之前要先在脑子里把这条链路拆开。常见做法是把流程切成六段入口展示、拉起授权页、用户授权确认、回调处理、Token 换发与信息拉取、登录态建立。每一段出问题表现出来的用户症状完全不一样。入口展示对应的是应用内QQ 登录按钮是否正常出现图标是否加载未安装 QQ 时是否提示拉起授权页对应的是应用能否正确唤起 QQ 客户端或 Web 授权页用户授权确认覆盖用户点同意和拒绝两条分支回调处理是授权后能否正确回到应用携带的 code 和 state 参数是否完整Token 换发是后端拿着 code 换 access_token 并拉取 openid 和用户信息的过程登录态建立则是应用把用户信息落库、写入会话用户在应用内看到已登录。这样拆完你就会发现日常测试说的登录成功其实只覆盖了第六段和第四段的一部分前面几段才是最容易翻车的区域。用例设计时我一般按每段至少两条正反向用例来铺正向走通反向分别走取消、拒绝、网络中断、参数缺失。2.2 核心测试点授权码是一次性的state 是用来防 CSRF 的QQ 登录走的是 OAuth 2.0 授权码模式核心机制有三个Authorization Code 是一次性的、state 参数用于防止跨站请求伪造、access_token 有有效期且可能被刷新。设计用例时这三个点必须单独列出来测因为它们最容易出现开发以为没影响、测试以为不用管的死角。先说一次性。授权码从授权回调中获取应用后端拿它去换 Token。测过的同学都知道同一个 code 换两次 Token 必定报错但很多团队只测了换一次成功没测重复提交场景。等到线上用户双击回调请求、或前端重复发送回调通知时后端直接抛异常用户看到的就是登录失败。所以用例里要有同 code 连续换两次、第二次必须返回明确错误码这条。再说 state。授权页拉起时前端要生成一个随机字符串塞进 state回调时校验它是否一致。这个机制专门拦 CSRF 攻击属于安全测试的一部分。测试用例至少要覆盖回调 url 缺少 state、state 与前值不一致、state 过期重放。不少前端图省事把 state 写死成固定字符串这等于没防也算一条必须标记的缺陷。access_token 的有效期也要看通常以小时或天为单位。测试时不能只看换 Token 成功还要查 Token 过期后接口返回什么应用是被踢下线还是走静默刷新。实际业务中很多应用把 Token 存在客户端本地过期时间到了还在用旧 Token 请求用户信息接口这时候 QQ 服务端返回的是 401 或错误码应用如果没有全局拦截用户点击任何需要登录态的功能都会莫名其妙失败。2.3 用例维度拆解正向、反向、多端、弱网、风控表格展示我常用的一组 QQ 登录测试用例维度按优先级分层。日常排期紧张时优先保障 P0P1 按需抽测P2 至少覆盖一条代表用例。维度用例方向优先级预期结果正常授权QQ 客户端已登录点击授权同意P0回调携带 code 和 state登录态建立取消授权授权页点拒绝/取消P0应用回到登录页无多余会话未安装 QQ拉起授权时检测 QQ 客户端P1跳转 Web 授权页或明确提示引导重复回调同一 code 提交两次P0第二次返回错误不产生新会话state 异常不传/篡改/过期 stateP0拒绝授权请求并返回错误码多端登录QQ 在另一台设备登录后本端操作P1本端被踢下线或提示重新验证弱网授权2G/3G / 丢包 / 超时P1授权页加载超时给出重试入口登录不卡死历史登录态已有会话再次进登录页P2显示已登录不重复授权风控拦截异地区域/异常频率P1触发验证或提示账号不被拉黑多端登录这条要特别说明。QQ 登录测试不能只在同一台测试机上做要建一个测试设备矩阵覆盖 Android、iOS、Web、小程序至少四个端。真实用户最常见的场景是在手机上用 QQ 授权登录了一个应用又在电脑浏览器上登同一个应用这个测试用例要能验证后登录的端是否把前者踢下线或者显示该账号已在其他设备登录的提示。很多应用只实现了单端登录校验没做全链路通知导致前一个端会话残留用户两边都能看到已登录但数据不同步。2.4 拉取用户信息的字段校验不能漏授权成功后应用会拿到用户的 openid、昵称、头像、性别等信息。测试时不能只看拿到信息了要逐个字段核对openid 在不同应用间是否唯一、同一用户重复授权是否返回同一个 openid、用户修改 QQ 头像后应用内是否同步更新、昵称含特殊字符比如表情、繁体字、空白字符时是否展示正常。我有一次就栽在昵称上。测试账号昵称叫小明显示一切正常上线后有用户反馈昵称变成了问号排查发现是 mysql 建表用的 utf8mb3存不了 emoji 字符。从那以后我所有登录测试都会准备一组特殊字符账号纯 emoji 昵称、超长昵称、纯空格、藏文和阿拉伯文。这类用例不会花太多时间但能挡住一大半线上数据问题。拉取用户信息还有一条隐私安全的线应用是否拉取了超出业务必要范围的信息是否把 openid、unionid 拼进日志埋点。如果发现日志里打印了用户 openid 和手机号这不是功能缺陷是安全测试要提的合规风险必须记录下来上报。3. 自动化落地用接口层跑通 QQ 登录 OAuth 流程并校验登录态3.1 为什么优先选接口层而不是 UI 自动化很多测试新手接到登录测试的第一反应是上 Appium 或 Selenium 去点按钮。但登录这个场景恰恰不适合把 UI 自动化当主力原因很直接QQ 授权页是腾讯的页面UI 元素不受你的应用控制QQ 版本一更新授权页的控件 id 说变就变你的脚本就得重写。而且拉起 QQ 客户端这种操作在测试机上依赖已登录的 QQ 环境CI 流水线里很难稳定复现。我常用的落地路径是接口层自动化为主UI 自动化只做最小的冒烟验证。具体分工接口层负责把 OAuth 的 code 换 Token、refresh_token 刷新、拉取用户信息这几个关键过程跑成自动化脚本UI 层只验证按钮存在、点击拉起授权、授权后回跳登录成功这一条主路径。这样设计的好处是稳定性和覆盖率都能兼顾。接口层脚本跑得快、不受 UI 变化影响还能直接断言后端返回的状态码和关键字段UI 层虽然脆但能证明整条链路是通的。3.2 先手工拿一次 Authorization Code自动化的第一步是先手工走一遍授权流程拿到一个真实的 Authorization Code 用来调试。这里要注意Authorization Code 是一次性的每跑一次脚本就要重新去授权页拿一次所以脚本里不能写死 code。手工拿 code 的过程每家应用不一样常见做法是拿测试手机打开应用点击 QQ 登录授权后在抓包工具里找到回调 URL从 query 参数里把 code 抠出来。拿到之后可以先不用脚本直接在 Postman 里调一次换 Token 接口确认网络通了、参数对了再写自动化脚本。3.3 Python 脚本模拟授权码换 Token 并校验登录态下面是一段我常用的接口层自动化脚本用 Python requests 实现核心路径是模拟后端拿 code 换 access_token再拿 Token 拉用户信息最后断言关键字段。import requests import time import json # 沙箱测试环境申请的测试号实际跑的时候替换为自己的值 APP_ID 101000001 APP_KEY test_app_key_123456 REDIRECT_URI https://test.example.com/qq_callback TOKEN_URL https://graph.qq.com/oauth2.0/token OPENID_URL https://graph.qq.com/oauth2.0/me USERINFO_URL https://graph.qq.com/user/get_user_info # 根据实际抓包获取每次授权后 code 都不同 authorization_code AUTHORIZATION_CODE_FROM_UI def exchange_token(code): params { grant_type: authorization_code, client_id: APP_ID, client_secret: APP_KEY, code: code, redirect_uri: REDIRECT_URI, fmt: json, } resp requests.post(TOKEN_URL, paramsparams, timeout10) resp.raise_for_status() return resp.json() def get_openid(access_token): params {access_token: access_token, fmt: json} resp requests.get(OPENID_URL, paramsparams, timeout10) data resp.json() # openid 在返回的 json 里不同 fmt 下字段名可能不同 return data.get(openid) def get_user_info(access_token, app_id, openid): params { access_token: access_token, oauth_consumer_key: app_id, openid: openid, fmt: json, } resp requests.get(USERINFO_URL, paramsparams, timeout10) return resp.json() # 核心流程换 Token - 取 openid - 拉用户信息 - 断言登录态 token_data exchange_token(authorization_code) assert access_token in token_data, f换 Token 失败: {token_data} access_token token_data[access_token] expires_in token_data.get(expires_in, 0) refresh_token token_data.get(refresh_token, ) openid get_openid(access_token) assert openid, openid 为空用户信息无法关联 userinfo get_user_info(access_token, APP_ID, openid) assert userinfo.get(ret) 0, f拉取用户信息失败: {userinfo} assert userinfo.get(nickname), 昵称为空 print(json.dumps({ access_token: access_token, expires_in: expires_in, refresh_token: refresh_token, openid: openid, nickname: userinfo.get(nickname), }, ensure_asciiFalse))这段代码的逻辑分四步先拿 code 换 Token这一步返回 access_token 和有效期再用 access_token 调 openid 接口把用户身份固定下来然后拿 access_token 加 openid 调用户信息接口最后断言关键字段非空。参数说明是新手最容易犯错的点。redirect_uri必须和你在 QQ 互联平台填写的一致少一个斜杠都不行否则换 Token 接口直接报 200021 之类错误码。fmtjson建议带上不带的接口默认返回 urlencoded 格式解析起来会多一层坑。timeout10不能省QQ 接口在弱网环境响应可能很慢没有超时机制会让整个测试卡死在请求上。3.4 用 Pytest 把登录自动化跑成用例集有了上面的核心流程接着要做的是把脚本改造成 pytest 用例让每次登录测试都能重复跑。这里的关键是隔离测试数据每个用例跑之前重新拿 code用例之间互不依赖。import pytest import requests from login_flow import exchange_token, get_openid, get_user_info pytest.fixture(scopefunction) def user_session(): # 每个用例独立授权code 互不复用 code acquire_code_from_ui_or_mock() token_data exchange_token(code) assert access_token in token_data return token_data def test_exchange_token_success(user_session): assert user_session.get(expires_in, 0) 0 def test_get_user_info_with_token(user_session): access_token user_session[access_token] openid get_openid(access_token) info get_user_info(access_token, APP_ID, openid) assert info[ret] 0 assert len(info[nickname]) 0 def test_duplicate_code_rejected(): # 重复使用同一个 code必须报错而不是静默成功 code SAME_CODE with pytest.raises(Exception): exchange_token(code) exchange_token(code)pytest 的 fixture 设置了 function 级别作用域保证每个用例拿到的 code 都是独立的不会因为前一个用例消费了 code 影响后一个用例。test_duplicate_code_rejected这个方法是把同一个 code 连调两次预期第二次抛异常这条用例需要后端配合如果开发没有对 code 做消费校验它会直接暴露出来。自动化跑到这一步其实已经有了一个可以纳入 CI 的冒烟集。每次发布前把登录链路跑一遍比上线后再人工回归要省时间得多。后面可以按同样的思路补上弱网测试脚本用 fiddler 限速模拟丢包验证超时提示和后端重试机制。这也是热搜里fiddler弱网测试实际落地最常见的场景入口。4. 常见问题与翻车排查QQ登录测试最容易踩的 5 个坑4.1 线上登录失败开发说我本地是好的——环境配置项不一致现象功能测试环境登录一切正常一到预发布环境就登录失败报redirect_uri 与后台配置不一致。开发本地调试怎么也复现不了。原因这是 QQ 登录测试里最常见的环境配置翻车。QQ 互联平台的 AppID 和回调地址是分环境配的测试环境、预发布、生产用的是不同的应用号。但很多项目组只申请了一个测试 AppID预发布环境直接沿用了等腾讯侧配置的域名和当前环境的回调域名对不上授权就被服务端拒绝。解决测试人员在提测前先做一次环境自检把当前环境的 AppID、回调地址、应用包名列一张表去 QQ 互联平台逐个核对。发现不一致要找后端确认该用哪个应用号不要自作聪明帮开发改配置。这条排查经验我反复吃了好几次亏才长记性。4.2 用户反馈授权后一直转圈——回调地址被吞了参数现象用户点同意授权后应用一直转圈授权页没有跳回应用。部分手机能复现部分手机正常。原因回调 URL 拼接不规范。常见的情况是回调地址带了 query 参数比如https://example.com/callback?fromlogin腾讯在末尾追加?codexxxstatexxx时开发直接写了url base_url code state于是新参数把原有参数覆盖掉服务端解析不到 code。另一种情况是前端配置了https回调但测试机用http拉起浏览器拦截了 mixed content。解决检查和修复回调拼接逻辑改用URLSearchParams或Uri.Builder追加参数。测试时专门配一条回调地址本身就带参数的用例在多个浏览器和 WebView 内核里分别跑一遍。这个坑属于写代码时不会注意、出了问题又特别难排查的类型值得在用例设计里专门占一行。4.3 同一个账号反复授权头像却一直不更新——本地缓存策略现象用户修改 QQ 头像后回到应用头像还是旧图。测试同学用多个账号交叉验证有的账号更新了有的没有。原因应用客户端把用户头像做了本地缓存缓存 key 用的是 openid 加头像 URL。QQ 返回的头像 URL 里带了一个时间戳参数头像没变时 URL 不变头像变了 URL 会变。但有些应用的缓存策略没有把 URL 参数纳入 key导致换了头像也命中旧缓存。解决在测试用例里增加修改 QQ 头像后重新授权登录的场景至少覆盖冷启动和热启动两种状态。发现问题后让客户端同学把头像 URL 的完整字符串含参数作为缓存 key或者接服务端下发的头像版本号。这类问题定位不难但容易在回归的时候被忽略。4.4 自动化脚本偶发失败手动执行又正常——code 被提前消费现象pytest 跑登录用例十条里面有两条报code 已使用或过期。单独手动跑这两条又成功了看起来毫无规律。原因这不是脚本稳定性问题而是数据隔离没做好。可能是 CI 平台里多个定时任务共享了同一个测试账号前一个任务跑完已经消费掉了授权码后一个任务使用同样的 code 自然失败。还有可能是 fixture 的 scope 设置成了 module一个用例消费完 code后面的用例就断了。解决回归 fixture 作用域确保每个用例独立获取 code在 CI 配置里给不同任务分配不同的测试账号同时检查日志里是否有两个线程同时提交同一个 code。这类问题最大的迷惑性在于随机失败如果不看日志很容易被当成网络抖动修掉。4.5 登录测试接口全绿上线后被安全团队拦了——风控策略没测现象接口自动化全过功能测试也通过但在审核时被安全团队指出登录接口缺少频率限制有撞库风险。或者上线后被 QQ 侧风控拦截大量真实用户无法授权。原因登录类接口天然是攻击者的目标。测试如果只关注功能正确性不会去测同一个账号短时间内疯狂请求授权、不同 IP 反复登录这类场景。QQ 平台侧自身有风控测试设备或测试账号频繁操作会触发限制表现出来就是刚才还能登录现在突然登录不了换台设备就好了。解决接口测试脚本里补上频率控制用例比如同一 Token 连续请求 50 次确认应用侧是否启用了限流功能测试时不要拿同一个测试号连续登录几十次多备几个账号轮换使用遇到突然登录不了先检查是不是测试账号被临时冻结了。登录测试不只是验证能登录验证不该登录的登不进来同样是测试工作的一部分。5. 测试数据与环境管理账号池、风控与过期 Token 的处理技巧5.1 不能所有测试人员共用一个 QQ 测试号很多小团队测 QQ 登录是临时找同事的个人号扫码或者所有人共用公司注册的一个测试 QQ。前者的风险是会污染真实账号的授权记录后者的风险是风控极易触发。我一般建议测试组维护一个独立的测试账号池每个账号有明确的用途标签正常授权组、特殊字符昵称组、无头像组、禁止授权组。正常授权组至少 3 个账号用于并发和轮换特殊字符组覆盖 emoji、长昵称、空白字符禁止授权组用于测用户拒绝授权的场景。账号池统一由测试负责人管理用密码管理器记录不随个人离职而丢失。账号池的维护频率也要定下来。QQ 号长期不活跃可能被冻结需要每隔一段时间登录一次保持活跃状态。如果公司允许给测试账号绑定手机号和邮箱方便找回。这些看起来是小事实际能省掉大量临时找号的尴尬。5.2 一个简单的账号池分配脚本账号池管理不一定要上公司级的平台一个简单的 Python 脚本就够用。下面是账号池的基本数据结构与一个可用性校验函数。import json import time # 账号池文件 accounts.json 的结构示意 ACCOUNT_POOL [ {qq: 289000001, tag: normal, status: free, last_used: 0}, {qq: 289000002, tag: normal, status: free, last_used: 0}, {qq: 289000003, tag: emoji_nickname, status: free, last_used: 0}, ] def acquire_account(pool, tagnormal, cooldown_seconds300): now time.time() for acc in pool: if acc[tag] tag and acc[status] free: if now - acc[last_used] cooldown_seconds: acc[status] in_use acc[last_used] now return acc[qq] return None def release_account(pool, qq): for acc in pool: if acc[qq] qq: acc[status] free break这个脚本的核心是冷启动限制cooldown_seconds默认 300 秒也就是同一个账号两次使用之间至少间隔 5 分钟这是规避 QQ 侧风控的基本手段。实际执行时可以把冷却时间调长比如 10 到 15 分钟。参数说明tag用来区分账号分组status标记账号是否被占用last_used记录上次使用时间。脚本本身不重要重要的是让团队形成登录测试要用账号池、用完要释放的习惯。否则每次自动化跑批都把同一个账号打到限流最后查出来是测试自己把自己的测试环境搞崩了。5.3 access_token 过期时间要进测试断言很多自动化测试只断言换 Token 成功就结束了没有检查expires_in字段。这就导致 Token 过期场景一直没被覆盖。建议把 Token 过期时间当成测试数据来管理至少每周跑一次模拟 Token 过期的用例。模拟的办法有两种。一种是拿到 access_token 后在代码里强制 sleep 到过期时间再发起请求缺点是耗时另一种是直接改本机时间跳过有效期校验缺点是部分接口是用系统时间判断的改了时间可能引发其他问题。我常用的是第一种配合 pytest 的慢速标记把这类用例放在夜间回归里。Token 过期后的行为也是测试点。应用是弹出请重新登录还是静默刷新 Token如果应用实现了静默刷新要验证刷新 Token 的接口是否携带了 refresh_token刷新后旧的 access_token 是否立即失效。很多应用在这个环节是裸奔的旧 Token 在过期后竟然还能继续用这在安全测试里属于必须修的漏洞。5.4 各环境的数据要不要隔离测试环境、预发布、生产环境的数据清况完全不同。测试环境可以随便造号、任意改状态预发布环境最好和生产保持一致的数据口径生产环境无论如何不能写入垃圾数据。我做 QQ 登录测试时的硬性规矩是测试环境用独立的测试 AppID关联的测试账号池永远是那几个预发布环境至少回归一遍真实回调域名生产环境只做一次冒烟验证用真实的线上 AppID 和回调地址走通全链路测完立刻把测试账号退出。这条线上最容易出问题的不是技术而是谁往生产环境加了测试账号这种管理问题建议在测试报告里明确标注每个环境的数据用途。6. 验证与进阶确认 QQ 登录测试真的测到位的自检方法6.1 用一张覆盖率自检表检验测试深度测试做完最怕的是所有用例都绿了但上线还是出问题。这里分享一张覆盖率自检表每次登录测试收尾时对着过一遍。这张表不是给测试报告做装饰用的是拿来追问自己到底有没有覆盖到这个分支。自检项是否覆盖发现问题数授权页正常拉起与取消是0code 一次性校验是1state 缺失与篡改否待补Token 过期与刷新是0多端登录互踢否待补弱网环境授权是1风控拦截与解除否待补特殊字符昵称与头像同步是0表里否标记出来的项不是靠补用例就能马上解决的。比如多端登录互踢需要至少两台测试设备加一个测试 QQ 号这个投入不小但它是真实用户最常见的场景之一。如果时间不够至少保证手工跑一次如果时间充裕把这几个场景固化成自动化用例。6.2 断言有效性的自检故意改坏一个地方看测试能不能抓出来自动化测试最大陷阱是断言形同虚设。接口返回 200 不代表登录成功也许后端返回的错误信息也是 200。我每做完一轮登录自动化会做一次有效性的自我检查故意在代码里把一个字段的断言值改错比如把 openid 的字段名改成不存在的open_id_bad然后看测试用例是否会失败。如果用例依然通过说明断言根本没有生效这个测试没有任何保护价值。这个做法看起来很简单但实际执行时发现不少团队的自动化脚本早就因各种原因失效了只是每天 CI 都显示绿色大家都没注意到。建议把这套自检写进测试流程每个迭代做一次。6.3 用异常音频做边界验证鹈鹕测试思路热搜里有个词叫鹈鹕测试思路其实和登录测试很搭用一个不按常理出牌的输入去试探系统的边界。QQ 登录里的异常输入非常多我在做登录测试时有一个保留项目把回调 URL 里的 code 参数改成长度为 0 的空值、长度 1000 的乱码、包含 URL 编码字符的字符串每个都作为独立用例执行。这些异常输入在真实用户操作中发生的概率不大但渗透测试的同事会专门拿这类输入来打接口。登录接口如果对参数长度和格式没有校验轻则日志告警刷屏重则被写入脏数据。建议把一组固定的异常输入集放在测试账号池旁边每次迭代都跑一遍。QQ 登录测试做久了最大的体会是它和普通功能测试不一样的地方在于你测试的并不只是你自己的代码还要和腾讯侧的服务、风控策略、各种奇奇怪怪的 WebView 内核打交道。很多问题在测试环境测不出来是因为数据量不够、环境太干净、操作太规律。真正的登录质量是线上几万用户用出来的不是测试环境跑出来的。所以我现在的习惯是线上发布后每小时看一次登录成功率监控和错误码分布连续观察四小时一切正常才敢说自己把 QQ 登录测到位了。希望这篇笔记能帮你在测试这条路上少走几步弯路。本文还有配套的精品资源点击获取
返回列表