当前位置: 首页 > 测试知识 > Gatling认证会话管理:模拟用户登录、令牌刷新与并发会话
Gatling认证会话管理:模拟用户登录、令牌刷新与并发会话
2026-01-13 作者cwb 浏览次数4

Gatling模拟需要认证的用户行为涉及从登录、令牌使用、自动刷新到并发会话控制。


认证会话

模拟登录和令牌管理

建立认证会话的第一步。


执行登录提取令牌


scala

import io.gatling.core.Predef._

import io.gatling.http.Predef._


class AuthenticationSimulation extends Simulation {


  val httpProtocol = http.baseUrl("https://api.zmtests.com")


  // 1. 定义用户证据Feeder

  val credentialsFeeder = csv("data/credentials.csv").circular


  val loginScenario = scenario("用户登录和令牌管理")

    .feed(credentialsFeeder)

    // 2. 执行登录请求

    .exec(

      http("用户登录")

        .post("/auth/login")

        .body(StringBody("""{"username":"${username}", "password":"${password}"}""")).asJson

        .check( // 3. 重点:从响应中提取令牌并存入Session

          jsonPath("$.access_token").saveAs("accessToken"),

          jsonPath("$.refresh_token").optional.saveAs("refreshToken"),

          jsonPath("$.expires_in").optional.saveAs("tokenExpiresIn")

        )

    )

    .exec(session => {

      // 4. 记录登录成功时间,用于计算令牌过期

      println(s"用户 ${session("username").as[String]} 登录成功,令牌: ${session("accessToken").as[String].take(10)}...")

      session.set("loginTime", System.currentTimeMillis() / 1000)

    })

    .pause(1)

}


重点:使用 .check() 提取响应中的令牌,并用saveAs将其存入每个虚拟用户独立的Session中,后续请求通过 ${accessToken}来引用。


使用令牌调用认证API


scala

.exec(

  http("获取用户资料")

    .get("/api/user/profile")

    .header("Authorization", "Bearer ${accessToken}") // 从Session动态注入令牌

    .check(status.is(200))

)


令牌自动刷新

需要在令牌失效前主动刷新。


主动刷新在业务请求前检查并刷新

在业务操作前,通过计算判断令牌是不是需要刷新。


scala

// 假设令牌有效期为3600秒,我们设置阈值在剩余300秒时刷新

.exec(session => {

  val loginTime = session("loginTime").as[Long]

  val currentTime = System.currentTimeMillis() / 1000

  val tokenAge = currentTime - loginTime

  val shouldRefresh = session.contains("refreshToken") && tokenAge > (3600 - 300)

  session.set("shouldRefreshToken", shouldRefresh)

})

.doIf("${shouldRefreshToken}") { // 条件执行刷新

  exec(

    http("刷新访问令牌")

      .post("/auth/refresh")

      .body(StringBody("""{"refresh_token":"${refreshToken}"}""")).asJson

      .check(

        jsonPath("$.access_token").saveAs("accessToken"),

        jsonPath("$.refresh_token").optional.saveAs("refreshToken") // 更新refresh_token

      )

  )

  .exec(session => {

    // 重置登录时间

    println(s"用户 ${session("username").as[String]} 令牌已刷新")

    session.set("loginTime", System.currentTimeMillis() / 1000)

      .remove("shouldRefreshToken")

  })

}


被动刷新处理401未授权响应

当令牌已过期,请求返回401时,进行刷新并重试。


scala

import io.gatling.commons.validation.Validation

import io.gatling.core.action.builder.FeedBuilder


// 定义刷新令牌的链,可重用

val refreshTokenChain = exec(

  http("被动刷新令牌")

    .post("/auth/refresh")

    .body(StringBody("""{"refresh_token":"${refreshToken}"}""")).asJson

    .check(

      jsonPath("$.access_token").saveAs("accessToken"),

      jsonPath("$.refresh_token").optional.saveAs("refreshToken")

    )

)


// 在可能失败的请求中尝试恢复

.exec(

  http("安全调用敏感API")

    .get("/api/sensitive/data")

    .header("Authorization", "Bearer ${accessToken}")

    .check(status.in(200, 401)) // 允许401

    .check( // 如果是401,触发特定处理

      status.is(401).saveAs("isUnauthorized")

    )

)

.doIf("${isUnauthorized}") {

  exec(refreshTokenChain)

    .exec( // 重试原请求

      http("安全调用敏感API - 重试")

        .get("/api/sensitive/data")

        .header("Authorization", "Bearer ${accessToken}")

        .check(status.is(200))

    )

    .exec(session => session.remove("isUnauthorized"))

}


模拟并发用户和会话

不同用户有不同的认证状态和行为。


模拟混合用户类型


scala

// 定义不同用户情形

val registeredUserScenario = scenario("已注册用户")

  .exec(loginScenario) // 复用登录流程

  .exec(/* 已认证用户的操作 */)


val guestUserScenario = scenario("访客用户")

  .exec(/* 无需登录的公共操作 */)


// 在同一个测试中注入不同比例的用户

setUp(

  registeredUserScenario.inject(

    rampUsers(100).during(30)

  ),

  guestUserScenario.inject(

    rampUsers(50).during(30)

  )

).protocols(httpProtocol)


模拟用户登出和会话清理


scala

.exec(

  http("用户登出")

    .post("/auth/logout")

    .header("Authorization", "Bearer ${accessToken}")

    .check(status.is(204))

)

.exec(session => {

  println(s"用户 ${session("username").as[String]} 已登出")

  session.removeAll("accessToken", "refreshToken", "loginTime") // 清理会话中的敏感数据

})


建议

令牌刷新方法

避免:不要所有用户在同一时间刷新。引入随机延迟:.pause(Random.nextInt(10).seconds)。

区分短令牌和长令牌:在Session中分开存储,使用不同刷新思路。


Session管理

最小化Session数据:只存储必要数据,避免内存过度使用。

使用.transform预处理数据:在Feeder读取数据时即完成清洗和格式化。

及时清理:在登出或会话结束时,使用.remove或.removeAll清理敏感数据。


并发控制和资源管理

限制最大并发刷新数:使用throttle限制令牌刷新端点压力。


scala

setUp(

  loginScenario.inject(rampUsers(1000).during(60))

).throttle(

  reachRps(50).in(10), // 限制刷新接口最大50 RPS

  holdFor(2 minutes)

)


监控虚拟用户状态:通过Gatling日志或自定义钩子记录异常。


调试

记录重点信息:在.exec中打印令牌片段和状态。

证实Session状态:使用session => { println(session); session } 调试。

检查响应完整性:保证刷新后新令牌被正确存储。


Gatling实现认证会话管理是将令牌生命周期管理、条件性执行思路、差别化用户模拟和健壮的错误处理进行有机结合。


文章标签: 软件测试 测试工具
咨询软件测试