做接口测试的朋友大概率都经历过这种场景:登录接口跑通了,返回一串token,然后你打开下一个接口,把token从响应里复制出来,粘贴到请求头或请求参数里。接口一多,token一过期,整套流程就得重来一遍。手动复制粘贴不仅慢,还特别容易漏掉空格、截断字符串,甚至把测试环境token粘到生产环境请求里。Postman提供了环境变量机制,配合Tests脚本可以自动提取token并写入环境变量,之后所有接口用{{token}}引用即可,换环境、换账号、token刷新都只在一处生效。这篇内容就是围绕“postman-提取token值,添加到环境变量中”这件事,把从响应中取值、写入环境变量、后续引用、失效刷新、常见报错排查完整串一遍。适合刚接触Postman接口测试的新手,也适合已经用了一段时间但还在手动管理token的老手。
1. 为什么要把token塞进环境变量
1.1 手动管理token到底有多容易翻车
最早我做接口测试时,也是手动复制token。登录一次,拿到token,然后打开十几个接口挨个粘贴。表面上看没什么,但实际跑起来问题很多。第一,token通常有有效期,短则15分钟,长则几小时,一旦过期,所有接口返回401,你得重新登录、重新复制、重新替换,整个过程像流水线工人。第二,不同环境(开发、测试、预发)的token不一样,手动切换时很容易把测试环境的token带到预发环境,排查半天才发现是token串了。第三,团队协作时,有人把token写死在请求头里,提交到集合里,别人拉下来还得手动改。第四,token里如果有特殊字符,复制时可能漏掉或多了换行,请求直接失败,但肉眼很难发现。
环境变量的价值就在于把“变化的部分”抽出来。token是典型的会变、会过期、分环境的数据。把它放到环境变量里,请求本身只引用变量名,不关心具体值。登录接口负责生产token并更新变量,其余接口只管用。这样token换了,变量自动更新,所有引用它的请求都跟着变。对于需要反复执行的回归测试、自动化集合,这个机制几乎是必选项。
1.2 环境变量在Postman里的定位
Postman的变量系统其实分好几层:全局变量、环境变量、集合变量、局部变量。它们的作用域和优先级不同。环境变量是绑定在“环境”这个概念上的,你可以建“开发环境”“测试环境”“生产环境”,每个环境里放一组变量,比如base_url、username、password、token。切换环境时,变量值自动切换。这比全局变量更清晰,因为全局变量是所有集合共享的,容易命名冲突。集合变量则绑定在某个集合里,适合只服务于该集合的配置。
我们这里要做的“提取token并添加到环境变量”,通常是指把token写进当前选中的环境。这样在同一个环境下运行的所有请求都能读到。如果还没建环境,也可以先写全局变量,但更推荐建专门的环境。原因很简单:你迟早会有多个环境,提前用环境变量管理,后面迁移成本几乎为零。
1.3 环境变量、全局变量、集合变量怎么选
选哪种变量,主要看作用范围和复用需求。环境变量适合区分不同部署环境,比如测试环境token和预发环境token。全局变量适合放一些跨集合、跨环境的常量,比如公司统一的网关地址,但token不建议放全局,因为token和账号、环境强相关。集合变量适合只在一个集合内使用的配置,比如某个业务线的固定请求头。
如果只是为了快速验证,用全局变量也能跑通。但一旦要多人协作、多环境切换,环境变量明显更稳。另外,Postman的变量优先级是:局部变量 > 环境变量 > 集合变量 > 全局变量。也就是说,如果同一个名字在环境变量和全局变量里都存在,环境变量会覆盖全局变量。了解这个优先级,在排查“为什么变量值不对”时非常有用。
2. 从登录响应里捞出token的四种姿势
2.1 响应体是标准JSON:pm.response.json()最省事
大多数登录接口返回的是JSON,类似:
{ "code": 200, "message": "success", "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 7200 } }在Postman的Tests标签页里,可以用pm.response.json()把响应体解析成JavaScript对象。假设token在data.token,写法就是:
const res = pm.response.json(); pm.environment.set("token", res.data.token);第一行解析响应,第二行把取到的值写入当前环境变量。注意pm.environment.set的第一个参数是变量名,第二个参数是值。变量名建议用英文、下划线,避免空格和特殊字符。执行一次登录请求后,环境变量里的token就会被更新。后续请求在请求头里写{{token}},Postman会自动替换成实际值。
这里有个小细节:如果登录接口返回的token字段名不是token,而是access_token、jwt、auth_token,改成对应的路径即可。写脚本前最好先在Response面板里看一眼返回结构,别凭记忆写路径。路径写错时,res.data.token会变成undefined,环境变量会被设成字符串"undefined",后续请求就会带着一个假token到处跑,排查起来很浪费时间。
2.2 token藏在响应头:用pm.response.headers.get()
有些接口不把token放在响应体里,而是放在响应头,比如Authorization、X-Auth-Token、token。这时候解析JSON就取不到了。Postman提供了pm.response.headers.get("header-name")来读取响应头。写法:
const token = pm.response.headers.get("Authorization"); if (token) { pm.environment.set("token", token); }如果响应头里带的是Bearer xxxxx这种格式,而请求头里也需要Bearer {{token}},那你可以把整个值存进去,引用时写Bearer {{token}}。但更建议只存纯token部分,在请求头里统一加Bearer前缀,这样切换认证方式时不用改环境变量。可以用replace去掉前缀:
let rawToken = pm.response.headers.get("Authorization"); if (rawToken && rawToken.startsWith("Bearer ")) { rawToken = rawToken.slice(7); } pm.environment.set("token", rawToken);注意响应头名称大小写不敏感,但headers.get通常能正确匹配。如果取不到,可以先用console.log(pm.response.headers)把全部响应头打印出来,看看实际字段名是什么。
2.3 返回结构嵌套很深:一层层剥开
有些接口返回的token藏得很深,比如:
{ "result": { "auth": { "credentials": { "accessToken": "xxx" } } } }这时候直接写res.result.auth.credentials.accessToken就行。但如果中间某一层可能不存在,直接点下去会报错,脚本中断。稳妥的做法是逐层判断:
const res = pm.response.json(); if (res && res.result && res.result.auth && res.result.auth.credentials) { pm.environment.set("token", res.result.auth.credentials.accessToken); } else { console.log("未找到token路径"); }如果觉得这种判断太啰嗦,可以用可选链操作符(Postman内置的Node.js版本一般支持):
const token = res?.result?.auth?.credentials?.accessToken; if (token) { pm.environment.set("token", token); }可选链在路径不存在时返回undefined,不会抛异常。但要注意,如果Postman版本较老,可能不支持该语法,那就退回逐层判断。
2.4 非标准格式:正则表达式兜底
偶尔会遇到接口返回的不是JSON,而是HTML、纯文本,或者token混在一大段字符串里。比如返回<html>...token=abc123...</html>。这时候可以先用pm.response.text()拿到纯文本,再用正则提取:
const text = pm.response.text(); const match = text.match(/token=([a-zA-Z0-9._-]+)/); if (match && match[1]) { pm.environment.set("token", match[1]); }正则里的字符集要根据实际token格式调整。JWT通常由三段Base64Url字符串和点组成,可以用/[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/来匹配。但正则容易过度匹配或匹配不全,能用JSON解析就不要用正则。正则更适合做兜底方案,而且写完一定要用几个样本测试,确认不会把错误内容写进环境变量。
3. 把提取到的token写进环境变量
3.1 创建环境并手动初始化变量
在Postman左上角或右上角找到环境选择器,点击“管理环境”,新建一个环境,比如命名“测试环境”。在环境里添加变量base_url、token、username等。token的初始值可以留空,或者填一个占位符。创建好后,在运行请求前,记得在右上角下拉框里选中这个环境。只有选中了环境,pm.environment.set才会把值写进去;如果没选环境,脚本会报错或写到全局。
手动初始化变量的意义是让后续请求有地方引用。即使token初始为空,请求头里写{{token}}也不会导致Postman报错,只是发出去的值是空字符串。所以最好在登录后先跑一次提取脚本,再跑其他接口。对于自动化集合,可以把登录请求放在最前面,利用集合运行器的顺序执行。
3.2 在Tests脚本中动态写入环境变量
前面已经演示了写入的基本写法。这里补充几个实用变体。如果想把token同时写到环境变量和全局变量作为备份:
const res = pm.response.json(); const token = res.data.token; pm.environment.set("token", token); pm.globals.set("token_backup", token);如果想让变量名带环境前缀,比如test_token、prod_token,可以在脚本里根据环境名动态拼接。但更推荐用不同环境来隔离,而不是在同一个环境里堆多个token变量。
写入时还可以顺手记录过期时间。很多登录接口返回expires_in(秒),可以算出过期时间戳存起来,后续请求前判断是否快过期:
const res = pm.response.json(); pm.environment.set("token", res.data.token); const expiresIn = res.data.expires_in || 7200; const expireAt = Date.now() + expiresIn * 1000; pm.environment.set("token_expire_at", expireAt.toString());这样在Pre-request Script里就能读取token_expire_at,判断是否要重新登录。这个小技巧在长时间运行的集合里非常实用。
3.3 引用变量:{{token}}的写法与作用域
环境变量建好后,在请求的URL、Headers、Body、Params里都可以用{{token}}引用。比如在Headers里加一个Authorization,值为Bearer {{token}}。Postman在发送请求前会把{{token}}替换成当前环境里token的实际值。如果变量不存在,{{token}}会原样发送,服务端收到字面量{{token}},通常会返回401或400。
引用时注意几个点。第一,变量名不要加空格,{{ token }}在某些版本里可能不被识别。第二,如果token里包含特殊字符,Postman会原样替换,不会做URL编码,所以如果token要放在URL参数里,可能需要手动编码。第三,环境变量只在选中对应环境时生效,切换到“无环境”后,{{token}}就不会被替换。第四,请求头里如果已经有Authorization,脚本写入环境变量不会自动改请求头,请求头里的{{token}}才是读取点。
3.4 验证是否写入成功:控制台与变量面板
写完脚本后,怎么确认token真的写进去了?最直接的方法是点击Postman底部或菜单里的“Console”打开控制台,在脚本里加console.log(pm.environment.get("token")),发送请求后看控制台输出。如果输出的是token值,说明写入成功。另外,点击右上角环境选择器旁边的“眼睛”图标,可以快速查看当前环境的变量列表,里面会显示token的当前值。注意,变量面板显示的值可能被截断,长token需要点开或复制出来看。
如果环境变量面板里没有更新,先检查是否选中了环境,再检查脚本是否在Tests里执行了。有时候请求失败(比如登录返回500),pm.response.json()会解析失败,脚本中断,自然写不进去。可以在Tests开头加console.log(pm.response.code)确认请求是否成功。
4. token失效与自动续签的实战思路
4.1 判断token过期的常见信号
token过期最典型的表现是接口返回401 Unauthorized、403 Forbidden,或者业务码code: 401、token expired。在Postman里,你可以为每个请求加一个统一的断言,当响应码是401时提示重新登录。但更好的做法是在Pre-request Script里提前判断token是否快过期,避免请求发出去才失败。
如果登录接口返回了expires_in,前面已经存了token_expire_at。在Pre-request Script里可以这样判断:
const expireAt = pm.environment.get("token_expire_at"); if (expireAt && Date.now() > Number(expireAt) - 60000) { console.log("token即将过期,需要重新登录"); // 这里可以触发登录请求,或者设置一个标志 }减去60000毫秒是留1分钟缓冲,防止请求在传输过程中过期。如果接口没有返回过期时间,可以观察token的JWT payload里的exp字段。JWT的payload是Base64Url编码的,可以解码后取exp。但并不是所有token都是JWT,所以优先使用服务端返回的过期时间。
4.2 用Pre-request Script自动刷新token
Postman本身不能在一个请求的Pre-request Script里直接发另一个请求并等待响应(旧版本不支持,新版本有pm.sendRequest可以异步发送)。但pm.sendRequest可以在Pre-request Script里调用登录接口,拿到新token后再设置环境变量。不过要注意,Pre-request Script的执行是异步的,如果直接在里面发请求,主请求可能不会等待它完成。正确做法是用pm.sendRequest的回调,但回调完成后再发主请求需要一些技巧,通常更推荐用集合运行器把登录请求放在最前面,或者用Postman的“Pre-request Script”配合pm.sendRequest并接受异步顺序。
实际项目中,更稳的方式是:单独建一个“刷新token”请求,在集合运行器里把它排在所有业务请求之前。如果token过期,运行业务请求前先跑刷新请求。对于手动调试,可以在环境变量面板手动点刷新。对于自动化,可以写一个Pre-request Script,用pm.sendRequest发登录请求,在回调里设置环境变量,但主请求是否使用新token取决于执行时机。为了避免复杂性,建议把登录/刷新请求作为独立步骤。
4.3 JWT续签:双token机制在Postman里怎么模拟
现在很多系统用双token:access_token短期有效,refresh_token长期有效。当access_token过期时,用refresh_token去换新的access_token。在Postman里模拟这个流程,可以建两个环境变量:access_token和refresh_token。登录接口返回两者,分别在Tests里写入环境变量。当业务接口返回401时,用refresh_token调用刷新接口,刷新接口的Tests再把新的access_token写回环境变量。
这种机制下,环境变量里要存两个token,请求头里引用{{access_token}}。刷新接口本身不需要access_token,只需要refresh_token。如果refresh_token也过期了,那就只能重新登录。在Postman里可以建一个集合,把登录、刷新、业务请求串起来,用集合运行器按顺序执行,并在刷新请求的Tests里加断言,判断刷新是否成功。
4.4 避免死循环:刷新token的边界条件
自动刷新最怕死循环:业务接口401,触发刷新;刷新接口也401,又触发刷新。所以刷新逻辑一定要有终止条件。可以在环境变量里记一个refresh_attempted标志,刷新一次后置为true,如果再次401就不再自动刷新,而是提示手动登录。另外,刷新接口本身返回401时,不要再调用刷新,直接报错。
还有一个边界:并发请求同时发现token过期,可能会同时触发多次刷新,导致refresh_token被重复使用而失效。Postman集合运行器默认是顺序执行,所以这个问题不突出。如果使用pm.sendRequest异步刷新,要加锁或标志位。简单做法是:在Pre-request Script里检查一个is_refreshing变量,如果正在刷新就跳过。但这些属于进阶用法,新手先把手动刷新和顺序执行跑稳,再考虑自动化刷新。
5. 常见坑与排查清单
5.1 token取不到:响应不是JSON或路径写错
最常见的问题就是pm.response.json()报错,通常是因为响应不是合法JSON。比如登录失败时返回了HTML错误页,或者响应体为空。这时候脚本第一行就抛异常,后面的写入不会执行。解决办法是在解析前先判断响应状态码和内容类型:
if (pm.response.code === 200) { try { const res = pm.response.json(); if (res.data && res.data.token) { pm.environment.set("token", res.data.token); } else { console.log("响应中没有token字段"); } } catch (e) { console.log("解析JSON失败:" + e.message); } } else { console.log("登录请求失败,状态码:" + pm.response.code); }路径写错也很常见。比如实际是res.data.access_token,你写成了res.data.token,取到undefined。可以用console.log(JSON.stringify(res))把整个响应结构打印出来,对照着写路径。不要凭感觉猜字段名。
5.2 环境变量不生效:没选对环境或变量名拼错
有时候脚本执行了,控制台也打印了token,但后续请求还是401。先看右上角环境选择器是不是选对了环境。如果选了“No Environment”,pm.environment.set会报错,token写不进去。再看变量名是否一致:脚本里写的是token,请求头里写的是{{access_token}},那就取不到。Postman变量名区分大小写,Token和token是两个变量。另外,如果环境变量里已经有值,脚本写入会覆盖,但如果你在请求里手动写了固定值,就不会走变量替换。
还有一个小坑:环境变量面板里修改了值但没有保存,或者多个环境之间切换后忘了同步。建议在集合运行前先用“眼睛”图标确认当前环境的token值是最新的。
5.3 Postman汉化、安装与版本差异带来的小麻烦
Postman官方桌面版支持在设置里切换语言,较新版本可以在Settings里找到语言选项,切换成中文。但汉化后有些菜单名称和英文教程对不上,比如“环境”对应“Environment”,“测试”对应“Tests”。新手看教程时如果发现找不到对应菜单,可以先切回英文,或者对照中英文文档。另外,不同版本的Postman脚本API基本兼容,但极老版本可能不支持pm.response.json()之外的某些方法。如果遇到脚本报错,先检查版本,尽量用Postman 10以上的较新版本。
在线版Postman和桌面版功能大体一致,但环境变量和集合的同步依赖账号登录。如果团队用在线版协作,要确保环境变量在团队工作区里共享,否则别人拉取集合后没有对应环境,token变量为空。安装方面,从官网下载对应系统版本即可,不建议用来路不明的第三方打包版本,避免脚本执行环境被篡改。
5.4 团队协作时环境变量的同步问题
团队协作时,环境变量最好通过Postman的工作区共享,而不是每个人手动建。共享环境后,token这种敏感信息要注意权限控制,避免把生产token暴露给无关人员。实际做法是:环境变量只共享结构(变量名和初始占位符),具体token值由每个人登录后自动写入本地环境。Postman的环境变量支持“初始值”和“当前值”,初始值会同步到团队,当前值只保留在本地。把token的初始值留空,当前值由脚本写入,这样既共享了变量名,又不会泄露真实token。
如果团队用Git管理集合和环境文件,要确保导出的环境JSON里不包含真实token。导出时选择“导出环境”并检查内容,把敏感值清掉。更稳妥的方式是只导出集合,环境变量由每个成员自己建,或者用Postman的API动态注入。
6. 我的实操心得与效率提升技巧
6.1 用脚本一次性提取多个字段
登录接口除了token,通常还会返回用户ID、角色、过期时间等。与其写多个请求分别提取,不如在登录的Tests里一次性写入多个环境变量:
const res = pm.response.json(); if (res.data) { pm.environment.set("token", res.data.token); pm.environment.set("user_id", res.data.userId); pm.environment.set("role", res.data.role); pm.environment.set("token_expire_at", (Date.now() + (res.data.expires_in || 7200) * 1000).toString()); }这样后续请求可以直接用{{user_id}}、{{role}}构造参数,省去很多手动查找。注意变量值必须是字符串或可转换为字符串的类型,对象和数组不要直接塞进去,需要先JSON.stringify。
6.2 断言加日志双保险
写Tests脚本时,我习惯把“提取变量”和“断言响应”分开写。先断言登录成功,再提取token。断言失败时脚本可以提前返回,避免把错误响应里的字段写进环境变量。比如:
pm.test("登录状态码为200", function () { pm.response.to.have.status(200); }); if (pm.response.code !== 200) { return; } const res = pm.response.json(); pm.test("响应包含token", function () { pm.expect(res.data).to.have.property("token"); }); if (res.data && res.data.token) { pm.environment.set("token", res.data.token); console.log("token已更新:" + res.data.token.substring(0, 20) + "..."); }日志里只打印token前20个字符,避免完整token刷屏,也方便确认更新动作执行了。断言和日志结合,排查问题时能快速定位是请求失败、响应结构变化,还是脚本逻辑问题。
6.3 环境变量导出与分享的注意点
如果要把环境配置分享给同事,可以导出环境JSON,但一定要先清空token的当前值。Postman导出环境时,默认会导出当前值,如果token是真实有效的,就会泄露。正确做法是在导出前把token变量值清空,或者只导出集合,让同事自己建环境。团队协作更推荐用Postman的工作区共享环境,并设置权限,把敏感变量标记为“secret”类型。Postman较新版本支持秘密变量,在环境变量里把类型改为secret,值会被掩码显示,导出时也不会明文带出。
另外,环境变量多了以后,命名要有规范。比如统一用test_token、test_base_url,或者用env_token。避免用token1、token2这种无法辨认的名字。变量名清晰,后面维护集合时能省很多时间。
6.4 集合运行器里的执行顺序与数据传递
用集合运行器跑整个集合时,请求按文件夹顺序执行。把登录请求放在最前面,并确保它的Tests脚本写入了环境变量。后续请求引用{{token}}时,运行器会在每个请求发送前重新解析变量,所以登录写入的新token会被后续请求读到。如果登录请求本身失败,后续请求会带着旧token或空token执行,导致大面积401。可以在集合运行器的设置里勾选“遇到错误继续执行”还是“停止运行”,根据测试目的选择。
另外,集合运行器每次运行可以选择不同的环境,这样同一套集合可以在测试环境和预发环境之间切换。只要环境变量里的base_url和登录账号不同,集合本身不需要改。这就是把token和环境变量结合起来的最大好处:一次配置,多处复用,手动复制的时代可以翻篇了。
最后再分享一个小技巧:如果token字段在响应里是数组或者需要拼接,比如res.data[0].token,记得先判断数组长度。脚本里多写一行if (Array.isArray(res.data) && res.data.length > 0),能避免很多“undefined”写入环境变量的低级错误。这些细节在官方文档里不会写,但实际调试中遇到一次,就会记住很久。