如何在Gatling场景中仅执行一次登录并复用认证令牌?
问题原因排查与解决方案
一、csrfToken多次生成的原因
- 全局变量线程不安全:你定义的
csrfToken是类级别的全局变量,Gatling多虚拟用户并发运行时会共享该变量,导致值被频繁覆盖;同时当前场景每个用户都会完整执行「获取csrf→登录」流程,自然每个用户都会生成独立的csrfToken,不符合“仅一次登录复用令牌”的需求。 - 未实现全局复用登录会话:现有逻辑没有提前完成单次登录并共享会话,而是让每个用户重复执行登录步骤,必然导致csrfToken多次生成。
二、listingID为空的原因
- 房源未实际创建:当前流程仅调用
GET /addProperty进入添加页面,没有提交创建房源的POST请求,页面不会生成对应房源的编辑链接,自然无法捕获listingID。 - 正则匹配失效:即使页面存在编辑链接,正则表达式
<a class="" href="https://dev.com/update/(\\d+)">可能与实际HTML结构不匹配(比如class属性值、链接格式有差异),导致无法提取ID。 - 响应未验证:未检查
GET /addProperty的响应内容,无法确认目标链接是否存在。
三、解决方案
1. 实现单登录全局复用会话
通过Gatling的before钩子提前执行一次登录,保存会话信息(Cookie、csrfToken等),所有虚拟用户直接复用该会话:
- 在Simulation类中添加
before方法,单独执行登录流程并存储会话。 - 后续虚拟用户启动时直接继承该会话的所有数据,避免重复登录。
2. 修复listingID为空问题
- 补充房源创建步骤:添加提交房源信息的POST请求,完成实际创建操作,才能生成对应的listingID。
- 优化ID提取方式:从创建成功后的跳转响应头(如Location)提取ID,比从HTML页面匹配更可靠;若需从页面提取,改用CSS选择器替代正则(稳定性更高)。
- 添加响应检查:保存页面响应内容,用于排查匹配失败的原因。
3. 移除不安全的全局变量
放弃类级别的csrfToken变量,改用Gatling Session存储变量,避免并发冲突。
四、修正后的代码示例
public class PerfCopy extends Simulation { // 全局存储登录后的会话信息 private Session sharedSession; @Override public void before() { // 提前执行单次登录,获取共享会话 HttpProtocolBuilder httpProtocol = http .baseUrl("https://dev.com/") .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") .acceptLanguageHeader("en-US,en;q=0.5") .acceptEncodingHeader("gzip, deflate, br") .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0"); ScenarioBuilder loginScn = scenario("PreLogin") .exec(http("CSRF_TOKEN") .get("/login") .check(status().is(200)) .check(regex("<meta name=\"csrf-token\" content=\"(.*?)\"").saveAs("csrfToken"))) .exec(http("Login") .post("/login") .headers(Map.of("Content-Type", "application/x-www-form-urlencoded")) .formParam("_token", "#{csrfToken}") .formParam("email", "email@email.com") .formParam("password", "test@1234") .check(status().is(200))); // 执行登录场景并保存会话 sharedSession = loginScn.injectOpen(atOnceUsers(1)).protocols(httpProtocol).awaitResult(); } public PerfCopy() { HttpProtocolBuilder httpProtocol = http .baseUrl("https://dev.com/") .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") .acceptLanguageHeader("en-US,en;q=0.5") .acceptEncodingHeader("gzip, deflate, br") .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0"); ScenarioBuilder scn = scenario("MainScenario") // 复用提前登录的会话数据 .exec(session -> session.setAll(sharedSession.getAll())) // 提交房源创建请求(需根据实际接口调整参数) .exec(http("POST Add Listing") .post("/addProperty") .headers(Map.of("Content-Type", "application/x-www-form-urlencoded")) .formParam("_token", "#{csrfToken}") .formParam("listing_owner", "152") .formParam("address-autocomplete-street-number", "123") .formParam("address-autocomplete-street-name", "Main St") .formParam("choose_mls[]", "Georgia") // 其他必填房源参数 .check(status().is(302)) // 创建成功通常会跳转 .check(header("Location").saveAs("listingUrl"))) // 从跳转链接提取listingID .exec(session -> { String listingUrl = session.getString("listingUrl"); String listingID = listingUrl.split("/")[listingUrl.split("/").length - 1]; return session.set("listingID", listingID); }) // 验证listingID .exec(session -> { String listingID = session.getString("listingID"); System.out.println("Listing ID: " + listingID); return session; }) // 更新房源信息 .exec(http("POST Info") .post("/update/${listingID}") .headers(Map.of( "Content-Type", "application/json", "X-CSRF-TOKEN", "#{csrfToken}" )) .body(ElFileBody("property_info.json")) .check(status().is(200)) .check(bodyString().saveAs("updateListingResponse"))) .exec(session -> { String responseBody = session.getString("updateListingResponse"); System.out.println("POST updateListingResponse Listing Media Response Body: " + responseBody); return session; }); // 配置虚拟用户数 int numUsers = 10; setUp(scn.injectOpen(rampUsers(numUsers).during(1)).protocols(httpProtocol)); } }
关键说明
- 全局会话复用:通过
before钩子提前完成单次登录,所有虚拟用户直接继承登录后的Cookie和csrfToken,彻底避免重复登录和csrfToken多次生成的问题。 - 房源创建逻辑:补充了POST提交房源的步骤,确保实际生成房源后再提取listingID。
- ID提取优化:从跳转响应头提取ID,比HTML页面匹配更可靠,避免正则匹配失效的问题。
内容的提问来源于stack exchange,提问作者Harshit Soni
相关产品推荐
相关产品推荐

