ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

接口自动化测试登录态保持:Cookie与Session绕过验证码实战

接口自动化测试登录态保持:Cookie与Session绕过验证码实战 做接口自动化测试这些年我踩过最大的坑之一就是登录态维护。自动化脚本跑到一半突然返回“未登录”或者“验证码错误”整个人都麻了。尤其是在测试环境有登录验证码、又要批量跑接口用例的场景下如果每次执行都靠手动登录拿凭证那自动化连“半自动”都算不上。我当时的解法很简单但很有效让脚本先用一组账号完成登录拿到后端下发的Cookie再用这套Cookie去跑后续所有需要登录态的接口用例。注意这里说的“绕过验证码”不是去破解验证码识别算法而是“不触发验证码”——只要会话保持在有效期内服务端就认为你是可信用户自然就不会每次请求都逼你过验证码。这套思路在Java接口自动化测试框架、Python requests脚本、Postman/JMeter里都通用。这篇内容适合给正在搭建接口自动化体系、被登录态和验证码卡住的测试开发同学看。我会从Cookie和Session的原理讲起再给你一套可以从零落地复现的实操步骤最后把我在实际项目里踩过的锅、排查过的坑一并整理出来。1. 为什么接口自动化会撞上验证码这道墙1.1 接口测试里最磨人的不是断言是登录态很多刚接触接口自动化的朋友第一步会先写一个“获取token”或者“登录”的公共方法然后其他用例先去调它拿到凭证后再去请求目标接口。这套逻辑听起来没什么问题但落到实际项目里往往会被一个东西劝退登录接口本身就套着一个图形验证码。图形验证码设计出来就是给“人”看的机器默认是过不了的。但自动化测试跑起来就是机器在跑你总不能每轮回归都蹲在电脑前人工看验证码、手动输进去吧那还谈什么自动执行、持续集成。所以很多团队的解决方案就是“绕开验证码”这个环节。所谓绕开不是绕过服务端的安全校验而是直接跳过“触发验证码”的流程——你只要有一个有效的登录凭证Cookie或者Token服务端会直接信任这个会话整个测试期间都不用再碰验证码。1.2 验证码到底拦的是什么风险验证码的核心目的是区分“正常用户操作”和“脚本批量操作”防止撞库、防止批量注册、防止刷接口。它的目标对象是异常流量不是我们的自动化测试。但服务端可不会因为你是“测试脚本”就网开一面。只要你在短时间用同一个IP发起大量登录请求风控系统就会把你识别成可疑流量验证码会越出越难。这个机制是保护系统安全的本身没问题问题在于测试执行时我们根本不应该走“高频登录”这条路径而是应该复用一个已经建立好的会话。这就是Cookie方案的立足点登录一次拿会话后续所有请求自动携带这个会话凭证不再触发风控判断。从产品角度看这也更接近真实用户的使用方式——你打开网站登录一次之后一整天都不用重复登录。1.3 先想清楚你要解决的是“绕过”还是“不触发”这里必须分辨清楚。网上搜索“cookie绕过验证码自动登录”很多人第一反应是识别验证码——比如用OCR、打码平台去破解图形验证码。这个方向我不推荐原因有三个识别率不稳定图形稍微变换、加点干扰线识别率立刻下降直接对验证码做识别属于对抗风控的行为在公司内部项目中容易踩合规红线工程上不值得你花一周去调OCR模型不如花半天把登录态管理做好效果更好也更稳定。更可靠的思路是让自动化脚本通过“合法登录流程”拿到会话凭证然后保持这个会话去执行测试。验证码在工程上不是用来“破”的而是用来“绕开触发条件”的。这个思路也是最安全的——你是在一个被授权的会话里做测试而不是试图攻破某个防护机制。2. Cookie与Session保持登录状态的两大核心机制2.1 Cookie到底是个什么东西Cookie是服务器下发、浏览器/客户端存储的一小段文本数据。简单类比就像你去健身房办了一张会员卡第一次登记完信息后前台给你一张卡。之后你每次进门不用再重新登记直接刷卡就行。Cookie就是那张卡。在HTTP协议里服务端可以通过响应头Set-Cookie下发Cookie浏览器收到后存下来。之后每次请求同一域名浏览器自动把Cookie放到请求头Cookie里发给服务端。服务端一看这个Cookie里的sessionId有效就知道“哦是你”不会再追问你密码和验证码。对接口自动化测试来说我们要做的事情就变成了模拟“浏览器收到Set-Cookie并保存”这个过程然后在后续请求里把Cookie带上。2.2 Cookie的四个关键属性不懂会踩大坑很多初学者只关心Cookie的key和value但实际使用中下面这几个属性才是决定“为什么我带了Cookie还是没登录态”的元凶。DomainCookie生效的域名。比如Domain.example.com表示这个Cookie在example.com的所有子域名下都能用如果只指定Domainlogin.example.com那你在api.example.com下发请求时这个Cookie根本不会被带上。PathCookie生效的路径范围默认是/表示全站有效。如果你只把Cookie设置到/login路径下那其他路径的请求也带不上。Expires / Max-Age过期时间。Expires是具体的时间点Max-Age是相对秒数。没有这两个属性时Cookie是会话级Cookie也就是浏览器关了就没。对自动化来说我们需要知道这个过期时间到底有多长——有些系统登录状态30分钟就过期有些能保持7天这个直接决定了你要不要加“自动重新登录”的逻辑。HttpOnly这个属性主要在安全层面起作用它表示Cookie不能被document.cookie读到防止XSS攻击拿到会话凭证。HttpOnly不影响HTTP请求自动携带Cookie所以即使Cookie里有HttpOnly标记你的接口测试依然能用它。还有一个属性是SameSite它限制跨站请求时是否携带Cookie。在接口自动化场景里如果你是自己拼Cookie发请求一般不受SameSite影响但如果你是通过浏览器自动化工具比如Selenium来操作那就要留意这个属性对跨域访问的限制。2.3 Session与Token“保持登录”不止Cookie一种玩法Cookie是“客户端保存凭证”的实现方式Session是“服务端保存状态”的一种方案Token是另一种无状态认证方案。很多人搞混我整理了一个表机制数据存哪服务端有状态吗典型场景Cookie Session凭证在客户端状态在服务端有状态需要Session存储传统Web项目、单体应用Token如JWT客户端全程持有服务端验签无状态前后端分离、微服务架构Cookie TokenToken放Cookie里由浏览器自动带无状态常见的Web应用折中方案接口自动化里如果你测的是纯Token接口那处理起来更简单把Token放到请求头Authorization: Bearer token就行。但如果你测的是传统Web项目后端用的是Session机制那你就离不开Cookie——因为Session的标识sessionId就是放在Cookie里传给客户端的。所以我一直建议进入一个项目先问清楚后端认证机制是什么。如果是Session就把Cookie维护好如果是JWT就维护好Token刷新如果是两者结合那么Cookie和Token你都得管。3. 三种拿Cookie的方式按使用场景选3.1 手动方式浏览器F12直接复制最快的方式适合“我就想先跑通一个用例”的场景。打开浏览器登录系统F12打开开发者工具切到Network面板随便找一个请求在Request Headers里找到Cookie字段全选复制。然后把这段字符串粘贴到测试脚本或者Postman的Cookie管理里。这套方法的好处是零成本、上手快缺点是Cookie会过期而且每次换账号、重新登录都要手动复制维护成本很高。只适合临时验证不适合做持续回归。3.2 半自动方式抓包工具导出如果你需要多个账号的Cookie或者要一次性把Cookie给团队多个成员用可以通过抓包工具来导出。Charles、Fiddler都能做到——登录一次过滤Set-Cookie响应头把关键会话Cookie复制出来分发。这种方法比F12多了一个好处你能直观看到服务端到底下发哪些Cookie、域名是什么、过期时间是什么排查“为什么Cookie没生效”时非常有用。但本质上还是手动操作不适合跑CI流水线。3.3 全自动方式用代码登录并提取Set-Cookie这是我要重点推荐的方式也是接口自动化框架里“自动登录”的标准实现。思路是测试脚本启动后先调用登录接口——如果登录接口需要验证码先想办法在测试环境“关掉验证码校验”或者使用“测试专用万能验证码”这个后面专门讲登录成功后从响应头里提取Set-Cookie存到全局的Cookie容器里后续所有请求自动带上这些Cookie。这里有一个很关键的技术细节很多登录接口返回的Set-Cookie不止一个可能有sessionId、可能有rememberMe、可能有额外的业务Cookie。你要注意代码里不能只取第一个——通行的做法是循环取出所有Set-Cookie合并后交给HTTP客户端统一管理。我用Java的HttpClient举一个简单的实现示例import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.Header; public class LoginWithCookie { public static void main(String[] args) throws Exception { // 1. 创建CookieStore相当于浏览器的Cookie仓库 BasicCookieStore cookieStore new BasicCookieStore(); CloseableHttpClient client HttpClients.custom() .setDefaultCookieStore(cookieStore) .build(); // 2. 调用登录接口 HttpPost loginPost new HttpPost(https://api.example.com/login); loginPost.setHeader(Content-Type, application/json); loginPost.setEntity(new StringEntity({\username\:\tester01\,\password\:\123456\})); client.execute(loginPost, response - { System.out.println(登录状态码: response.getCode()); // 读取Set-Cookie头 Header[] headers response.getHeaders(Set-Cookie); for (Header h : headers) { System.out.println(Set-Cookie: h.getValue()); } // 这里不需要手动解析CookieHttpClient的CookieStore会自动存储 return null; }); // 3. 后续请求自动携带Cookie HttpGet getOrder new HttpGet(https://api.example.com/user/orders); client.execute(getOrder, response - { String body EntityUtils.toString(response.getEntity()); System.out.println(订单接口返回: body); return null; }); client.close(); } }这段代码的关键在于BasicCookieStore——它就是你的“Cookie仓库”。HttpClient发送的每个请求都会自动从这个仓库里捞匹配的Cookie放进去不需要你手工拼Cookie请求头。这一步做到了登录态就真的“保持”住了。4. 在接口自动化框架中管好Cookie4.1 先决定框架再决定Cookie管理方式做Java接口自动化测试最常见的是两种HTTP客户端组合HttpClientApache HttpComponents灵活、底层、可控RestAssured基于Groovy/Java的REST测试库写校验断言非常方便。另外还有很多人用OkHttp不过测试领域用前面两个更多。不管你选哪个Cookie管理的核心只有一条让每个请求共享同一个Cookie上下文。如果你今天在登录用例里存了Cookie明天在另一个测试类里取不到那不是工具的问题是“上下文共享”没做对。4.2 方案一HttpClient的CookieStore全局管理在HttpClient里CookieStore负责保存Cookie。你可以在测试框架里把它做成单例或者通过依赖注入传给所有需要发请求的类。注意一个很常见的问题如果你用多线程跑用例不要让所有线程共用一个非线程安全的CookieStore旧版本里CookieStore可能不是线程安全的高版本如5.x的BasicCookieStore是线程安全的但仍不建议不同账号混用。更好的方案是每个线程持有自己的CookieStore但共享同一个登录方法。4.3 方案二RestAssured的cookie管理RestAssured用起来更省心默认的RestAssured.given()不保存Cookie但你可以在登录后把Cookie提取出来放到一个全局的过滤器里。import io.restassured.RestAssured; import io.restassured.filter.cookie.CookieFilter; import io.restassured.response.Response; public class RestAssuredLogin { // 全局Cooike过滤器所有请求共用 private static CookieFilter cookieFilter new CookieFilter(); public static void login() { Response response RestAssured.given() .filter(cookieFilter) .contentType(application/json) .body({\username\:\tester01\,\password\:\123456\}) .post(https://api.example.com/login); System.out.println(状态码: response.getStatusCode()); } public static void getUserInfo() { // 后续请求同样附加filter自动携带登录后的Cookie RestAssured.given() .filter(cookieFilter) .get(https://api.example.com/user/info) .then() .statusCode(200) .log().body(); } }CookieFilter会自动捕获响应里的Set-Cookie并在后续请求中自动回放。如果你的测试类都继承同一个基类在基类的BeforeClass里调用login()那每个测试类跑的时候都会先登录一次Cookie就自然共享了。4.4 多线程执行时Cookie怎么共享才不出乱子很多团队的接口用例是用TestNG或JUnit并行跑的。这时候如果所有线程共用一个账号的Cookie容易出现两个问题接口有并发限制一个账号被多线程高频调用被风控拦截A线程重新登录后B线程正在用的旧Cookie还没失效但服务端Session可能被顶号新登录把旧Session踢掉。我的经验是给测试数据池准备多个账号每个线程绑定一个独立账号同时每个账号单独维护一个CookieStore。这样既避免了Cookie互相覆盖也减小了触发风控的概率。5. 验证码场景的工程处理思路5.1 测试环境第一原则能关就关回到“绕过验证码”这个点。如果你在测试环境做接口自动化最省事、最稳妥、也最合规的方式不是研究怎么识别验证码而是让验证码“不出现”。具体做法是找开发配合在测试环境增加一个开关所有自动化测试账号走“免验证码”通道。实现方式很多比如IP白名单、请求头标记、指定测试账号直接跳过验证码校验。这不是绕过系统的安全防护而是测试环境为了可测试性做的合理配置属于测试基建的一部分。我接触过的很多团队测试环境里验证码模块压根没启用因为验证码会给手工测试和联调也添一堆麻烦。如果你所在的测试环境还在强制验证码先跟开发聊聊大概率能在环境配置上解决。5.2 登录接口带验证码时的几种内部处理手段如果开发表示测试环境不方便改代码那也有几种工程手段可以过渡方案优点缺点适用场景找开发要万能验证码实现最简单测试固定输这个码就能过需要开发配合且只能在测试环境保留测试环境接口自动化、联调在测试数据准备阶段绕过验证码通过后端API直接造登录态不经过登录页需要额外写数据构造接口持续集成、大批量执行用带有“记住我”功能的Cookie登录一次长期有效不需要频繁重登Cookie会过期过期后还得重新获取回归频率不高的小项目OCR识别验证码全自动不依赖开发识别率不稳定维护成本高不推荐除非环境无法调整我自己最常用的是第一种和第二种的组合先用万能验证码把自动化账号的会话拿到然后把会话存下来复用会话快到过期时间前再用万能验证码重新登录一次。5.3 Cookie失效之后的自动重新登录机制Cookie总有过期的时候。如果测试执行中途Cookie过期了后续用例就会报401或者跳登录。好的框架要能“自动续命”。这里有两种设计思路前置检查每个测试用例执行前先调一个轻量接口比如获取用户信息验证Cookie是否有效如果失效就重新登录。缺点是多了一次额外请求拖慢执行速度。拦截响应在HTTP客户端加一个拦截器当请求返回401/302时自动触发重新登录然后重放刚才失败的请求。这个思路最优雅对用例无感知但实现复杂度稍高。我个人推荐优先做“拦截响应”方案。用HttpClient实现时可以写一个HttpRequestInterceptor或者包装一层执行逻辑判断响应状态码若为401则调用登录方法刷新Cookie再克隆请求重放一次。多花两天开发时间但能省掉后面无数个“跑不过就手动重新登录”的夜晚。6. 常见问题与排查记录6.1 登录成功但后续请求还是401这是我被问得最多的一个问题。原因通常不在Cookie本身而在于Cookie的域名或路径不匹配。比如你在login.example.com登录拿到的Cookie的Domain是.example.com那在api.example.com发请求没问题但有些系统会把Domain设成login.example.com那这个Cookie只能在登录域名下使用拿到API域名下发请求自然无效。排查方法用抓包工具看登录接口返回的Set-Cookie内容确认Domain和Path再看API请求的域名是否匹配。如果确实不匹配要么改Cookie的Domain要么在代码里对Cookie值做字符串替换后手动放到请求头发送。6.2 重定向后Cookie丢了有些接口登录成功后服务端会返回302重定向浏览器会自动跟随重定向并同步Cookie。但如果你在代码里用的HTTP客户端默认不开启“自动重定向Cookie”就会出现“登录成功但会话没保持住”的假象。HttpClient 5里可以通过策略配置重定向时的Cookie携带行为。更简单的办法关掉自动重定向手工处理302逻辑登录时先记录Set-Cookie再根据Location重新发起请求。这样每一步都在你的控制之下定位问题也更方便。6.3 Cookie一直失效先看Expires有一种看起来很诡异的现象手工复制浏览器Cookie到脚本里能用但过一两个小时就失效了。原因很可能是Cookie的过期时间很短或者Session在服务端设置了空闲超时。建议在所有需要维持会话的测试里先打印出Set-Cookie的Expires/Max-Age和服务端Session超时配置按这个时间倒推你的执行策略。如果单次回归超过会话时长就必须规划“中途重新登录”的逻辑。6.4 排查结论从哪个日志开始查最后给一套排查链路。遇到Cookie相关问题时别瞎猜按顺序查登录响应的Set-Cookie内容是否包含你需要的会话标识Cookie的Domain、Path是否与目标请求域名匹配请求头里是否真的带上了Cookie在HttpClient里开启debug日志或者临时打印请求头请求是否经过代理或网关导致Cookie被过滤服务端Session是否被新的登录顶掉或者已经过期。按这个顺序查下来90%的Cookie问题都能定位。剩下10%多半是服务端配置问题这时把前面的日志整理好直接提工单给开发效率也最高。最后分享一点我自己的体会Cookie这套东西表面看是“复制粘贴一下”的事但真正稳定跑起来需要你把背后的属性和机制吃透。我一开始也试图用OCR去破验证码折腾了一周识别率还是时好时坏后来换了思路把精力花在Cookie管理和会话保持上整个接口自动化框架一下就顺了。做测试开发这行最重要的不是跟机制硬刚而是找到最合适的路径绕过不必要的问题。Cookie保持登录态就是那条最稳、最合规、也最容易落地的路。如果让我总结一条最值得记住的经验那就是验证码不是用来破解的而是用来避免触发的。你把登录态保持好了验证码的问题基本上就消失了一半——剩下那一半交给测试环境配置去解决就够了。这套方案我在多个项目里跑了好几年稳定可靠。
返回列表