第四章. 关联:搞定 Spring Security 认证

第四章. 关联:搞定 Spring Security 认证

摘要:本章我们将攻克性能测试中最大的拦路虎——接口依赖。我们将模拟真实的 “登录 -> 获取 Token -> 携带 Token 访问受保护接口” 的业务闭环,掌握 JSON 提取器和正则表达式提取器的核心用法,实现跨请求的数据传递。

本章学习路径

我们将按照以下步骤构建动态的业务链路:

  • 4.1 场景与靶场升级
    • 4.1.1 什么是 “关联” (Correlation)
    • 4.1.2 模拟 Spring Security 登录与鉴权
  • 4.2 JSON 提取器实战
    • 4.2.1 JSONPath 语法速成
    • 4.2.2 提取 Token 变量
  • 4.3 跨请求传递
    • 4.3.1 HTTP 信息头管理器的使用
    • 4.3.2 调试与验证变量传递
  • 4.4 正则表达式提取器(进阶)
    • 4.4.1 万能的 Regex 语法
    • 4.4.2 提取响应头中的 Cookie

4.1. 场景与靶场升级:模拟真实的业务链

在上一章,我们通过随机参数模拟了独立的查询请求。但在现实世界中,90% 的业务操作都需要先 “登录”。

在 Spring Boot + Spring Security 的架构中,通常采用 JWT (JSON Web Token) 机制:

  1. 客户端调用 /login,服务端校验账号密码,返回一个加密字符串(Token)。
  2. 客户端在后续请求头中携带 Authorization: Bearer {Token}
  3. 服务端拦截器校验 Token 有效性。

为了在 JMeter 中实现这一过程,我们需要先升级我们的 Spring Boot 靶场。

4.1.1. 编写模拟鉴权接口

为了专注于 JMeter 本身,我们不引入笨重的 Spring Security 依赖,而是手动编写代码模拟这一行为。

文件路径src/main/java/com/demo/jmeterdemo/controller/AuthController.java

请创建新的控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.demo.jmeterdemo.controller;

import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

// 模拟一个简单的内存 Token 存储,用于验证
private static final String VALID_TOKEN_PREFIX = "Bearer ";

/**
* 1. 登录接口
* 作用:接收账号密码,返回 Token
*/
@PostMapping("/login")
public Map<String, Object> login(@RequestBody Map<String, String> user) {
String username = user.get("username");
String password = user.get("password");

Map<String, Object> result = new HashMap<>();

// 模拟简单的账号校验
if ("admin".equals(username) && "123456".equals(password)) {
result.put("code", 200);
result.put("msg", "Login Success");
// 生成一个模拟的 Token (真实场景中是 JWT 字符串)
result.put("token", UUID.randomUUID().toString());
} else {
result.put("code", 401);
result.put("msg", "Invalid Credentials");
}
return result;
}

/**
* 2. 受保护的订单接口
* 作用:必须携带 Token 才能访问
*/
@GetMapping("/order/create")
public Map<String, Object> createOrder(@RequestHeader(value = "Authorization", required = false) String authHeader) {
Map<String, Object> result = new HashMap<>();

// 校验 Token 是否存在
if (authHeader == null || !authHeader.startsWith(VALID_TOKEN_PREFIX)) {
result.put("code", 403);
result.put("msg", "Forbidden: Missing or Invalid Token");
return result;
}

// 校验通过,执行业务
result.put("code", 200);
result.put("msg", "Order Created Successfully");
result.put("orderId", System.currentTimeMillis());
return result;
}
}

重启项目,我们有了两个新目标:

  1. POST /api/auth/login:获取 Token。
  2. GET /api/auth/order/create:需要 Token 才能访问。

4.2. JSON 提取器实战:抓取 Token

现在回到 JMeter。如果我们直接按顺序调用这两个接口,第二个接口必然报错(403 Forbidden),因为 JMeter 默认是一个 “健忘” 的客户端,它不会自动把上一次请求的响应带到下一次。

我们需要手动建立关联,这一步在 JMeter 中通过 后置处理器 (Post Processor) 实现。

4.2.1. 第一步:配置登录请求

  1. 创建一个新的 线程组,命名为 " S01_下单流程 "。

  2. 添加 HTTP 请求,命名为 " API_登录 "。

配置如下参数:

  • Method: POST
  • Path: /api/auth/login
  • 消息体数据:
1
2
3
4
{
"username": "admin",
"password": "123456"
}
  1. 重要:因为我们要发送 JSON Body,必须添加 HTTP 信息头管理器 (HTTP Header Manager)

配置步骤如下:

右键点击 " API_登录 " -> 添加 -> 配置元件 -> HTTP 信息头管理器。

添加一行:Content-Type = application/json

4.2.2. 第二步:添加 JSON 提取器

我们的目标是从登录接口返回的 JSON 中提取 token 字段的值。

1
2
3
4
5
{
"code": 200,
"msg": "Login Success",
"token": "550e8400-e29b-41d4-a716-446655440000" <-- 我们要抓取这个
}

操作步骤

  1. 右键点击 " API_登录 " -> 添加 -> 后置处理器 -> JSON 提取器 (JSON Extractor)

  2. 配置核心参数:

参数名配置值解释
变量名称 (Names of created variables)jwt_token给提取出来的值起个名字,后续用 ${jwt_token} 引用。
JSON 路径表达式 (JSON Path expressions)$.token$ 代表根节点,.token 代表下一级 key。
匹配号 (Match No.)1如果有多个 token,取第 1 个。0 代表随机,-1 代表全部。
默认值 (Default Value)TOKEN_NOT_FOUND如果提取失败,变量会被赋值为这个字符串(便于排查错误)。

4.3. 跨请求传递:使用 Token

拿到了 Token,下一步是将其 “注入” 到下单接口的请求头中。

4.3.1. 配置下单请求

  1. 在线程组下添加第二个 HTTP 请求,命名为 " API_创建订单 "。

配置如下参数:

  • Method: GET
  • Path: /api/auth/order/create
  1. 关键步骤:添加请求头。

操作步骤如下:

  • 右键点击 " API_创建订单 " -> 添加 -> 配置元件 -> HTTP 信息头管理器
  • 添加一行配置:
      • 名称*: Authorization
      • *: Bearer ${jwt_token}

注意:这里的值必须严格遵循后端代码的校验规则(即前缀 Bearer + 空格 + 变量)。

4.3.2. 调试与验证

点击启动运行测试,观察 察看结果树

  1. API_登录:响应数据中包含 Token。

  2. API_创建订单

验证步骤如下:

  • 点击 请求 (Request) 选项卡 -> Request Headers
  • 你应该能看到 Authorization: Bearer 550e8400...
  • 如果看到 Authorization: Bearer TOKEN_NOT_FOUND,说明 JSON 提取器配置有误(通常是 JSONPath 写错了)。
  • 如果响应状态码为 200msg: "Order Created Successfully",说明关联成功!

image-20251119114736843


4.4. 正则表达式提取器:处理非 JSON 场景

在现代前后端分离开发中,JSON 确实是主流。但在企业级开发中,我们经常会遇到 “老旧系统”(Legacy System)或第三方回调接口,它们返回的可能不是标准的 JSON,而是 XML、HTML 甚至纯文本字符串。此外,像 Set-CookieLocation 重定向地址等关键信息,往往隐藏在响应头(Response Headers)而非响应体中。

面对这些场景,JSON 提取器就失效了。这时,我们需要请出 JMeter 的万能军刀——正则表达式提取器

4.4.1. 场景升级:模拟 “老旧” 接口

为了演示这个场景,我们需要在 Spring Boot 靶场中增加一个模拟的老旧接口。它不返回 JSON,而是返回一段具有特定格式的字符串。

修改文件src/main/java/com/demo/jmeterdemo/controller/AuthController.java

请在 AuthController 类中添加以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 3. 模拟老旧系统的状态查询接口
* 返回格式:纯文本 "User: admin|SessionID: abc-123-xyz|Status: Active"
* 场景:我们需要提取 SessionID 用于后续操作
*/
@GetMapping("/legacy/status")
public String getLegacyStatus() {
// 模拟生成一个动态的 SessionID
String sessionId = "sess_" + System.currentTimeMillis();
// 返回非 JSON 格式的字符串
return String.format("User:admin|SessionID:%s|Status:Active", sessionId);
}

重启项目,访问 http://localhost:8080/api/auth/legacy/status,你会看到类似这样的响应:
User:admin|SessionID:sess_1731981234567|Status:Active

挑战目标:我们需要从这串文本中精准提取出 sess_1731981234567

4.4.2. 核心语法拆解

在使用提取器之前,我们必须先搞懂正则表达式在 JMeter 中的特殊语法。

我们要提取的目标是 SessionID:| 之间的内容。

正则表达式公式SessionID:(.*?)\|

  • SessionID:左边界。告诉 JMeter 从哪里开始找(锚点)。
  • ()捕获组。括号里的内容就是我们真正想要的数据。
  • .:匹配任意字符。
  • *:匹配 0 次或多次。
  • ?非贪婪模式(关键!)。
    • 如果不加 ?(贪婪模式),它会一直匹配到这一行的最后一个 |
    • 加上 ?,它会匹配到 最近的 一个 | 就停止。
  • \|右边界。因为 | 在正则中是特殊字符(表示 “或”),所以需要用 \ 转义。

4.4.3. 实战配置:提取 SessionID

现在我们来配置 JMeter 脚本。

操作步骤

  1. 添加请求:在线程组下添加一个新的 HTTP 请求,命名为 " API_老旧接口 "。
  • Path: /api/auth/legacy/status
    * Method: GET
  1. 添加提取器:右键点击 " API_老旧接口 " -> 添加 -> 后置处理器 -> 正则表达式提取器 (Regular Expression Extractor)

  2. 详细配置(请严格按照下表填写):

参数项配置值深度原理解析
要检查的响应字段主体 (Body)因为我们的数据在 Response Body 里。如果要提取 Cookie,这里需选 信息头
引用名称legacy_sess提取后的变量名,后续用 ${legacy_sess} 使用。
正则表达式SessionID:(.*?)|见上文的语法拆解。
模板 (Template)$1$$1$ 表示提取第 1 个括号对捕获的内容。如果是 $0$ 则表示提取整个表达式(包含边界)。
匹配数字 (Match No.)1如果响应中有多个 SessionID,填 1 取第一个;填 0 会随机取一个(常用于随机点击链接)。
缺省值ERR_SESS最佳实践:永远给一个显眼的错误默认值,方便后续 Debug。

4.4.4. 验证与调试

提取配置好了,但正则很容易写错(比如漏了转义符)。我们如何验证它是否工作正常?

方法一:使用 Debug Sampler(推荐)

  1. 在线程组中添加一个 调试取样器 (Debug Sampler)(位于:添加 -> 取样器 -> Debug Sampler)。
    • 这个组件的作用是把当前线程所有的变量都打印出来。
  2. 运行脚本。
  3. 察看结果树 中点击 Debug Sampler 的响应数据。
  4. 搜索 legacy_sess
    • 成功legacy_sess=sess_1731981234567
    • 失败legacy_sess=ERR_SESS(此时需要回头检查正则表达式)

方法二:正则测试器(RegExp Tester)

JMeter 的 “察看结果树” 自带了一个正则测试工具,不需要反复运行脚本 即可调试。

  1. 点击 察看结果树,选择 " API_老旧接口 " 的请求记录。
  2. 将结果树面板中的下拉框(默认是 Text)切换为 RegExp Tester
  3. Regular expression 输入框中填入 SessionID:(.*?)\|
  4. 点击 Test 按钮。
  5. 界面下方会直接显示提取结果。这是调试复杂正则最高效的方法。

4.5. 本章小结

在本章中,我们攻克了性能测试中 “数据流通” 的难题。

核心要点

  1. JSON 提取器:处理标准 REST API 的首选,简单高效,认准 $.key 语法。
  2. 正则提取器:处理非标数据(如老旧系统、HTML、Header 头信息)的终极方案。
    • 口诀左边界(想要的内容)右边界
    • 注意:慎用贪婪模式,善用 ? 限制匹配范围。
  3. 调试技巧:善用 Debug Sampler 查看变量池,或利用 RegExp Tester 实时验证正则逻辑。

下一步计划:现在我们的脚本已经能够成功登录并提取 Token 和 SessionID。但是,如果后端突然报错了(HTTP 500),或者返回了 {"code": 20001, "msg": "库存不足"},JMeter 默认还是会显示 “绿色盾牌”(因为 HTTP 状态码是 200)。这会导致我们误判测试结果。在下一章,我们将学习 断言 (Assertion),给 JMeter 装上 “火眼金睛”,让它能识别真正的业务成功。