1. 项目概述压测中的身份验证拦路虎做性能压测的朋友尤其是刚接触JMeter的十有八九都卡在过“登录拿token”这个环节上。你辛辛苦苦写好了业务接口的脚本一跑起来结果全是401、403接口告诉你“身份验证失败”。问题就出在你模拟的成千上万个虚拟用户手里都没有那张进入系统大门的“门票”——也就是Token。这就像组织一场大型活动你只给第一个检票的人发了通行证后面成千上万的观众都被拦在了门外这压力测试根本没法进行。这个项目要解决的就是如何让JMeter在压测过程中自动、高效、准确地为每一个虚拟用户线程获取并携带其独立的Token。核心在于利用JMeter的“JSON提取器”这个后置处理器从登录接口的响应中像用镊子夹取特定物品一样精准地提取出Token值然后通过“HTTP信息头管理器”将其添加到后续所有请求的请求头中。整个过程必须是动态的、线程安全的确保每个用户会话都是独立的。如果你正在为如何让JMeter模拟真实用户登录状态而头疼或者你的压测脚本总是因为认证问题而中断那么接下来这套从原理到实操的完整方案就是为你准备的。2. 核心思路与方案设计2.1 为什么不能手动复制粘贴Token很多新手会犯一个错误先用浏览器或Postman登录一次手动从响应里复制出Token然后把它硬编码到JMeter脚本的HTTP信息头管理器里。这种做法在功能测试时或许可行但在压测场景下是完全错误的。压测的核心是模拟大量并发用户。每个用户都应该有自己独立的会话和身份。如果你所有线程都使用同一个Token会引发一系列问题会话冲突服务器端可能会认为这是同一个用户在疯狂重复操作从而触发安全策略封禁该Token。数据污染如果操作涉及用户数据如“我的订单”所有线程都会操作同一个用户的数据这与真实场景不符测试结果无效。无法测试登录性能你完全绕过了登录接口也就无法评估登录接口在高并发下的响应时间、吞吐量和错误率而这往往是性能瓶颈所在。因此我们的方案必须是每个虚拟用户JMeter线程在执行业务操作前先独立调用一次登录接口获取属于自己的Token并在其后续的请求生命周期中使用这个Token。2.2 组件选型为什么是JSON提取器JMeter提供了多种后置处理器来提取响应数据比如正则表达式提取器、边界提取器、CSS选择器提取器和JSON提取器。对于现代基于RESTful API、返回JSON格式数据的登录接口JSON提取器是首选也是最佳实践。它的优势非常明显精准直观直接通过JSONPath表达式定位节点无需像正则表达式那样处理复杂的转义和匹配规则。例如如果响应体是{code: 200, data: {token: eyJhbGciOiJ...}}你只需要写$.data.token就能直接取到Token值。可读性强JSONPath语法易于理解和维护即使脚本交给别人也能一眼看懂提取逻辑。稳定性高相对于正则表达式对响应格式的微小变化可能导致的匹配失败JSON提取器基于结构解析更为健壮。当然如果接口返回的是XML或者非标准格式的文本正则表达式提取器仍是必要的备用选项。但鉴于当前绝大多数API都采用JSON我们将重点放在JSON提取器上。2.3 线程组设计与逻辑控制器编排要让每个用户先登录再操作我们需要规划好线程组的结构。通常采用“仅一次控制器”或“登录事务控制器”来包裹登录请求。方案一仅一次控制器将登录请求放在“仅一次控制器”下。这样每个线程在启动时会执行一次该控制器内的所有请求然后再去执行控制器外的请求业务操作。这保证了每个用户只登录一次然后进行多次业务迭代。这适用于“登录-执行一系列业务-退出”的场景。方案二独立的事务控制器将登录请求单独放在一个“事务控制器”中并为其命名如“用户登录”。这样在聚合报告里你可以清晰看到登录这个步骤的性能指标。业务请求放在另一个事务控制器或直接在线程组下。通过线程组的循环次数来控制业务操作的次数。我个人的习惯是使用“仅一次控制器”因为它逻辑清晰能准确表达“每个线程初始化时执行一次”的语义。在本方案的后续步骤中我将以此为基础进行演示。3. 实操构建从登录到携带Token的完整流程下面我们一步步搭建这个压测脚本。假设我们有一个登录接口POST /api/auth/login成功返回JSON格式的Token。3.1 第一步创建线程组与登录请求添加线程组右键测试计划 - 添加 - 线程用户 - 线程组。设置线程数虚拟用户数、Ramp-Up时间和循环次数。例如设置线程数为50Ramp-Up时间为10秒循环次数为1业务循环通过后面的逻辑控制。添加仅一次控制器右键线程组 - 添加 - 逻辑控制器 - 仅一次控制器。在仅一次控制器下添加HTTP请求名称用户登录协议http/https服务器名称或IP填写你的被测系统域名或IP端口号默认80或443或你的应用端口HTTP请求POST路径/api/auth/login在“消息体数据”或“参数”选项卡中填写登录请求体。通常是JSON格式例如{ username: ${__RandomString(10,abcdefghijklmnopqrstuvwxyz0123456789,USER_)}, password: testPassword123 }这里我使用了JMeter函数__RandomString来生成随机的用户名模拟不同用户登录。你也可以使用CSV数据文件来读取预设的用户名密码对这更接近真实情况。3.2 第二步添加JSON提取器获取Token在“用户登录”这个HTTP请求上右键 - 添加 - 后置处理器 - JSON提取器。我们需要配置以下几个关键字段名称提取登录Token自定义便于识别Apply to通常保持默认Main sample and sub-samples即可。Names of created variablesauth_token。这是你为提取的值定义的变量名后面会用到。JSON Path expressions$.data.token。这是JSONPath表达式需要根据你接口的实际返回结构来写。假设登录成功响应为{code: 0, message: success, data: {token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..., userId: 1001}}那么表达式$.data.token就能精准定位到token字符串。Match No.1。如果返回的data.token是一个值填1。如果它是一个数组极少数情况你想取第几个就填几或者填0表示随机取一个-1表示取所有。Compute concatenation var如果匹配到多个值当Match No.为-1时是否用分隔符拼接成一个字符串变量。此处不需要空着。Default ValuesNOT_FOUND。如果提取失败比如JSONPath写错了或者接口返回错误变量会被赋予这个默认值。这有助于调试。关键提示如何确定JSONPath表达式强烈建议先用一个调试用的HTTP请求或使用“查看结果树”监听器跑一次登录看到完整的响应体后再根据实际结构来编写。你也可以使用在线JSONPath验证工具来测试你的表达式是否正确。3.3 第三步添加调试取样器验证提取结果在JSON提取器后面但在仅一次控制器结束前右键 - 添加 - 取样器 - 调试取样器。运行脚本后在“查看结果树”中查看这个调试取样器的响应它会把当前JMeter上下文中的所有变量都打印出来。你应该能看到一个名为auth_token的变量其值就是提取到的Token字符串。这是验证提取是否成功的最直接方法。3.4 第四步为业务请求添加HTTP信息头管理器现在Token已经存储在变量auth_token中了。我们需要让后续的所有业务请求都能在请求头中带上它。在线程组层级或业务事务控制器层级添加HTTP信息头管理器右键线程组或你的业务逻辑控制器- 添加 - 配置元件 - HTTP信息头管理器。放在这个层级意味着其下的所有HTTP请求都会继承这个头信息。配置信息头在信息头管理器中添加一个头。名称Authorization这是最常见的名称具体以你的接口文档为准也可能是token、X-Access-Token等。值Bearer ${auth_token}这是JWT Token的标准携带方式。如果你的接口只需要Token值则直接填${auth_token}。重要原理JMeter的变量作用域。定义在某个请求下的后置处理器如JSON提取器中创建的变量其作用域是该请求所属的控制器乃至整个线程组。因此在线程组层级添加的HTTP信息头管理器可以访问到在“仅一次控制器”内创建的auth_token变量。每个线程都有自己独立的变量副本所以不会互相干扰。3.5 第五步构建业务请求并运行验证现在你可以在线程组下仅一次控制器之外添加你的业务HTTP请求了比如“查询用户信息”、“提交订单”等。由于它们继承了线程组层级的HTTP信息头管理器所以会自动在请求头中携带Authorization: Bearer ${auth_token}。添加一个“查看结果树”监听器运行测试。你应该看到第一个请求是“用户登录”响应码为200响应体中有Token。后续的业务请求在“请求”标签页中查看“HTTP请求头”应该能看到Authorization头并且其值是正确的Token。所有业务请求的响应码也应该是200或预期的成功码而不是401。4. 高级技巧与深度优化4.1 处理动态参数与关联有时登录接口需要额外的动态参数比如图形验证码、滑动验证的token或者请求签名。这些可能需要从登录页面先获取。验证码可以事先准备一个验证码识别服务对于压测有时可以让开发提供万能验证码或关闭验证码校验或者使用OCR插件但更常见的压测做法是让开发在测试环境屏蔽验证码逻辑。请求签名Sign这通常是一个由多个参数包括Token、时间戳、请求体等按照特定算法生成的字符串。你需要使用JMeter的JSR223 预处理器或BeanShell 预处理器来编写代码计算签名。将计算出的签名值存入一个变量如sign然后在HTTP请求的参数或头中引用${sign}。// 假设需要将 token、timestamp、请求体拼接后做MD5 import java.security.MessageDigest def timestamp System.currentTimeMillis(); vars.put(timestamp, timestamp.toString()); def token vars.get(auth_token); def requestBody sampler.getArguments().getArgument(0).getValue(); // 获取请求体 def stringToSign token timestamp requestBody; def md MessageDigest.getInstance(MD5); md.update(stringToSign.getBytes()); byte[] digest md.digest(); def sign digest.encodeHex().toString(); vars.put(sign, sign);4.2 Token过期与自动刷新策略某些系统的Token有较短的有效期如2小时。在长时间稳定性压测如24小时中Token可能会过期。解决方案使用While 控制器和If 控制器实现条件重登录。将业务操作比如“查询-等待-再查询”放在一个“While控制器”内条件设为true或一个控制循环次数的变量。在While控制器内部业务请求之前添加一个“If 控制器”。If控制器的条件可以设置为${__jexl3(${auth_token} NOT_FOUND || ${__timeShift(,,PT-30M,)} ${token_timestamp},)}。这个条件比较复杂解释一下auth_token NOT_FOUND是初始状态或提取失败。更实际的是判断Token是否临近过期。我们可以在成功登录时用一个变量token_timestamp记录登录时间戳${__time(,)}。然后在If条件中判断当前时间是否超过了token_timestamp 有效期 - 缓冲时间例如提前30分钟刷新。在If控制器内部放置你的登录请求和JSON提取器。这样当条件满足时线程就会重新执行登录获取新的Token并更新auth_token和token_timestamp变量。这是一个相对高级的用法需要仔细设计逻辑避免出现所有线程同时重登录造成的“惊群效应”。4.3 使用CSV数据文件实现多用户登录硬编码或随机生成用户不是长久之计。更真实的压测需要模拟不同的真实用户。创建一个CSV文件如users.csv包含至少两列username,password。每行一个用户。user1,pass1 user2,pass2 ...在线程组下添加CSV 数据文件设置配置元件。文件名指向你的users.csv路径。文件编码UTF-8变量名称username,password用逗号分隔对应CSV的列其他设置遇到文件结束符再次循环?选True用户数不够线程数时循环使用遇到文件结束符停止线程?选False。修改登录HTTP请求中的用户名和密码参数用户名${username}密码${password}确保“线程组” - “线程属性” - 勾选“独立运行每个线程组”如果只有一个线程组则忽略。这样每个线程在读取CSV文件时会获取独立的一行数据实现真正的多用户并发登录。5. 常见问题排查与性能调优5.1 问题排查清单当你发现业务请求返回401时请按以下顺序排查问题现象可能原因排查步骤登录请求失败接口地址、参数、请求方式错误1. 在“查看结果树”中检查登录请求的“请求”和“响应数据”。2. 确认请求体JSON格式正确无多余逗号。3. 确认服务器和端口号正确。JSON提取器未提取到Token1. JSONPath表达式错误。2. 登录响应非JSON格式。3. 登录实际失败返回错误码。1. 使用调试取样器查看变量auth_token是否为NOT_FOUND。2. 检查登录响应体确认JSON结构使用在线工具验证JSONPath。3. 检查登录请求的响应码和消息确认登录成功。业务请求头中无Token1. HTTP信息头管理器未正确添加或位置不对。2. 变量名拼写错误。1. 检查信息头管理器的作用域应在线程组或业务控制器层级。2. 在业务请求的“查看结果树”-“请求”标签中检查发送出去的HTTP头看是否有Authorization头及其值。确认值是${auth_token}而不是字面字符串。Token已过期Token有效期短压测运行时间长。检查业务请求的响应信息看是否有“token expired”等提示。需要实现上述的Token自动刷新逻辑。高并发下登录失败率高1. 登录接口本身性能瓶颈。2. 测试机资源端口、网络耗尽。3. 服务器端会话管理或数据库连接池瓶颈。1. 先单独对登录接口做压测评估其性能。2. 在JMeter的jmeter.properties中调整httpclient4.time_to_live和httpclient4.max_total_connections等参数。3. 观察服务器监控CPU、内存、数据库连接数、慢查询。5.2 性能调优建议连接复用确保HTTP请求默认值或HTTP请求中启用了“KeepAlive”。这能显著减少TCP三次握手的开销。合理设置超时在“HTTP请求”或“HTTP请求默认值”中设置合理的连接超时和响应超时如5000毫秒。避免因个别慢请求阻塞线程。禁用不必要的监听器“查看结果树”和“调试取样器”会消耗大量内存在正式压测时务必禁用或删除它们。只保留“聚合报告”、“汇总报告”、“用表格查看结果”等轻量级监听器。参数化与资源释放使用CSV数据文件时注意文件大小。对于海量用户考虑使用随机函数生成。确保测试数据不会成为瓶颈。在长时间压测后观察测试机内存使用情况。分布式压测当单台机器无法产生足够压力时使用JMeter分布式压测。需要特别注意主控机Master上定义的变量和属性默认不会发送到从机Slave。像CSV数据文件这类资源需要手动拷贝到所有Slave机器的相同路径下或者使用共享存储。5.3 一个容易被忽略的坑Cookie管理有些系统的认证信息既通过Token在Authorization头中传递也通过Cookie传递。JMeter默认的“HTTP Cookie管理器”会自动管理Cookie。但如果你在脚本中同时使用了Cookie和自定义的Authorization头需要确保两者是协调的。最佳实践如果接口明确使用Token头认证可以在线程组级别添加一个HTTP Cookie管理器并将其策略设置为compatibility或更严格的模式或者干脆在不需要Cookie的脚本中移除Cookie管理器避免不必要的干扰。检查方法在“查看结果树”中同时检查请求的“请求头”和“Cookie数据”确认没有发送冲突或冗余的认证信息。构建一个健壮的、能自动处理Token的JMeter压测脚本是进行有效性能测试的基础。从简单的JSON提取器应用到多用户参数化、Token刷新策略每一步都需要根据实际的系统认证机制进行适配和调试。记住压测脚本的核心目标是真实模拟用户行为。一个稳定、可靠的登录和Token传递逻辑是你获取可信性能数据的首要保障。多利用调试取样器和查看结果树进行验证在正式压测前做好充分的脚本调试和单用户验证能节省大量后期排查问题的时间。