前言
接口自动化项目里,登录鉴权经常是第一个看起来“应该不难”,但实际上最容易翻车的部分。
原因很简单:
-
真实项目的登录链路往往不止一步
-
一个系统可能不止一种 token
-
不同角色、不同租户、不同系统之间的鉴权方式都可能不同
-
token 失效、缓存污染、账号串用的问题非常常见
如果把这些逻辑直接写在每条用例里,项目很快就会变得不可维护。
所以在我的这个项目里,鉴权被单独抽成了一层,核心代码集中在 common/auth_manager.py、client/api_client.py 和 fixtures/api_fixtures.py。
这一篇我想讲的不是“如何登录”,而是为什么一定要把登录鉴权从用例中剥离出来,以及这样做到底解决了哪些真实问题。
一、先看真实的鉴权链路
这个项目不是一个简单的“登录后拿 Bearer Token”模式,而是有比较典型的企业级链路:
1)SSO 登录
先通过 SSO 登录接口拿到重定向地址,再从 URL 中解析授权码 code,最后用 code 去交换 ssoToken。
2)业务系统B 登录
在拿到 ssoToken 之后,再结合 tenant_id、client_id 等信息,进一步换取 系统B 即业务侧的 token。
3)业务系统访问
不同业务系统会在请求头中携带不同的默认头,比如:
-
system-code -
tenant-id -
client-id -
ssoToken -
Authorization
也就是说,在这个项目里,鉴权不是一个动作,而是一条链路。
二、为什么鉴权必须单独抽层
很多人做接口自动化时会忽略一个问题:
登录其实是最容易和业务测试耦合的地方。
如果你把登录流程直接写进测试用例,后果通常有这些:
1)用例可读性迅速下降
原本只是想测库存列表,结果用例前面先写了几十行登录逻辑。
2)登录逻辑很难统一修改
例如登录接口路径改了、返回结构改了、授权码获取逻辑改了,你得改所有用例。
3)token 复用能力差
大量用例执行时,如果每条都重新登录,速度慢不说,还容易引发风控或不稳定。
4)角色切换不清晰
普通用户、操作员、管理员、不同租户的 token 如果都写在用例里,后期很难区分谁在访问什么。
所以更合理的做法是:把登录归到基础设施层,而不是业务验证层。
三、AuthManager 的定位
common/auth_manager.py 里的 AuthManager,承担的是“统一拿 token、统一缓存 token、统一失效 token”的职责。
它用了一个比较实用的方式:
-
单例模式
-
内存缓存
-
按账号、系统、租户组合 key
核心想法很简单:
同一个账号、同一个系统、同一个租户,如果 token 还有效,就不要重复登录。
这样设计有几个明显好处:
1)避免重复登录
session 级测试执行时,一个 token 可以被大量用例复用。
2)减少登录接口压力
接口自动化本身不是压力测试,没必要因为测试框架设计不合理而频繁打登录接口。
3)让 token 生命周期更可控
缓存和失效都集中处理,出问题时更好定位。
4)便于扩展多账号体系
以后如果再增加测试账号,只要再补一组 key 规则就行,不需要改一堆用例。
四、SSO 登录为什么是“先 code 再 token”
项目中的 SSOClient 做的是比较典型的企业统一登录流程。
它不是直接调用一个接口拿 token,而是分成两步:
第一步:登录
通过用户名、密码、登录来源、系统编码等信息,拿到一个重定向 URL。
第二步:解析授权码并换 token
从 URL 中解析 code,再用 code 调换 ssoToken。
这一点的意义在于:
-
登录流程和 token 颁发流程被拆开了
-
登录失败和 token 交换失败可以更清晰地定位
-
兼容企业里常见的统一认证中心模式
对应代码就在 client/api_client.py 的 SSOClient.login() 和 SSOClient.exchange_token()。
五、为什么要把登录逻辑封装成方法而不是脚本
如果只是脚本,通常会出现这样的问题:
-
代码可读性差
-
登录参数散落
-
重定向解析到处复制
-
错误处理不统一
而封装成方法之后,测试框架会得到几个好处:
1)登录流程可复用
不管是默认用户还是操作员用户,都能复用同一套登录逻辑。
2)异常处理统一
如果 code 提取失败,或者 token 没取到,可以统一抛出明确错误。
3)便于替换实现
如果未来登录方式变了,比如由页面跳转改成接口直登,你只改一层封装,不需要动所有用例。
4)更符合测试基础设施思维
测试用例的重点应该是“业务验证”,不是“怎么登录”。
六、业务系统B Token 的获取为什么要单独处理
很多企业项目里,SSO Token 并不能直接访问所有业务系统。
这个项目里就有一个很典型的链路:
-
先拿 SSO Token
-
再基于 SSO Token 和租户信息去获取 业务系统B Token
-
最后带着 业务系统B Token 调业务接口
这个设计背后体现的是企业系统常见的权限边界:
-
统一认证中心负责身份确认
-
业务系统负责具体授权
-
租户信息负责隔离业务数据
所以 AuthManager._do_业务系统B_login() 的存在不是多余,而是把“身份”和“业务访问”这两层关系明确区分开。
如果不这样做,后面很多业务场景都会变得很难解释:
-
为什么同一个人能访问某些系统,不能访问另一些系统
-
为什么同一个 SSO Token 在不同租户下看到的数据不一样
-
为什么请求头里必须带
tenant-id
把这些问题抽出来,整个项目就清楚很多。
七、fixture 为什么是鉴权落地的最佳位置
这个项目在 fixtures/api_fixtures.py 里把鉴权做成了 pytest fixture。
这是非常合理的,因为 pytest 的 fixture 天生适合做“前置准备”。
例如:
@pytest.fixture(scope="session") def sso_token(): return AuthManager().get_sso_token(...)
这意味着:
-
一个 session 里 token 可以复用
-
用例里不需要重复写登录步骤
-
登录失败会在最早的地方暴露
又比如:
@pytest.fixture(scope="session") def 业务系统A_client(sso_token): client = 业务系统AClient() client.session.headers.update({"ssoToken": sso_token}) return client
这一步更重要,因为它把“已登录的业务客户端”直接注入给测试用例。
用例拿到的是一个已经准备好的 client,而不是自己重新拼 header。
八、多账号、多角色为什么要分开设计
这个项目里其实已经预留了两类账号:
-
默认用户
-
操作员用户
这类设计在企业系统里非常必要,因为不同角色看到的数据和权限往往不一样。
例如:
-
普通用户只能查自己权限范围内的数据
-
操作员可能可以看到更多库存或管理数据
-
不同租户的数据互相隔离
如果这些角色不分开,测试结论就会非常模糊。
所以你在 fixture 里单独做了 operator_sso_token 和 业务系统A_operator_client,这是非常符合实际业务的做法。
它的好处是:
-
角色边界清晰
-
fixture 命名清晰
-
用例意图清晰
-
后期扩展第三类账号也容易
九、鉴权里最容易踩的坑
1)缓存了 token,但没有考虑失效
缓存本身没问题,问题是缓存后忘了做失效处理。
如果 token 过期了,但你还一直拿缓存值用,就会出现一串无意义的失败。
所以 invalidate() 的存在是必要的。
2)登录态串号
如果多个系统共用同一个 session 或同一套 header,而默认头没有隔离好,就可能把 A 系统的 token 带到 B 系统里。
这个问题在多系统项目里非常常见。
3)重复登录导致不稳定
很多接口自动化一旦变慢,常常不是业务接口慢,而是登录流程重复执行太多。
4)测试数据和鉴权账号不匹配
例如账号权限不足,却要访问管理员数据,最后你会误以为接口失败,实际是账号不对。
所以账号、租户、系统、数据类型要一起设计。
十、为什么这个项目适合把鉴权做成“基础设施”
因为你的项目不是一个单系统小脚本,而是一个多系统、多角色、多租户的接口自动化框架。
在这种项目里,鉴权不是附属功能,而是底层能力。
如果底层没做好,后面所有业务场景都会被拖累。
反过来说,只要鉴权层稳定了:
-
用例会变短
-
服务层会更清晰
-
业务场景会更自然
-
报错会更好定位
这就是把鉴权单独做成基础设施的价值。
十一、小结
登录鉴权在接口自动化里,真正难的不是“怎么拿到 token”,而是:
-
怎么把登录流程从用例中剥离
-
怎么复用 token 而不串号
-
怎么支持多账号、多系统、多租户
-
怎么让登录失败尽早暴露
这个项目已经把这套思路做出来了,而且是比较符合企业落地的方式。
补充:解释下单例模式
一个类在整个进程里只创建一个实例,后面再创建时拿到的还是同一个对象。
在这个项目里,common/auth_manager.py 和 common/config_manager.py 都用了这种思路。
它解决什么问题
- 避免重复初始化
- 统一管理共享状态
- 方便做缓存,比如 token、配置、连接信息
在项目里的体现
- AuthManager 负责登录鉴权和 token 缓存
- ConfigManager 负责只加载一次配置文件
- 它们都通过 new 控制实例生成:
- 第一次创建时,生成对象
- 之后再 AuthManager() / ConfigManager(),返回同一个对象
你可以把它理解成:
a = AuthManager()
b = AuthManager()
assert a is b # True
为什么鉴权里常用单例
- token 通常希望全局复用,别每个用例都重新登录
- 配置文件也不想反复读取
- 鉴权流程比较重,单例能减少重复开销
但要注意
- 单例模式只保证“同一个进程里”只有一个实例
- 如果是多进程跑测试,比如 pytest-xdist,每个进程还是会各自有一个单例
- 单例适合“全局共享、状态稳定”的对象,不适合每个用例都应该隔离的对象
一句话总结
- 单例 = 保证一个类只有一个实例
- 在这个项目里,它主要用来做 配置统一加载 和 鉴权 token 统一缓存

500

被折叠的 条评论
为什么被折叠?



