如何调整k6负载测试流程,解决Liferay JSONWS认证失败问题
解决Liferay 7.1中k6负载测试调用JSONWS API的认证问题
问题背景
在为Liferay 7.1的WebSocket端点构建k6负载测试时,模拟浏览器Cookie认证流程(登录→获取JWT→初始化WebSocket)遇到异常:调用JSONWS的my-app.auth/create-token接口时,Liferay日志报错[JSONAction:92] User 34253 must be authenticated,请求被重定向至/generic.html后陷入404循环。已确认k6正确传递JSESSIONID及其他Cookie,且使用与浏览器测试相同的用户。
问题排查与调整方案
1. 禁止硬编码p_auth令牌
Liferay的p_auth令牌是会话绑定且一次性有效,不能复用首页的csrfToken或硬编码固定值(如代码中的xt6uBXfx)。需从登录后的响应中重新提取最新的p_auth值,用于后续JWT请求。
2. 同步会话状态与重定向处理
k6默认跟随重定向,但Liferay登录后的多次重定向(/c/→/)可能导致会话状态未完全同步。可显式控制重定向行为,确保每次请求携带最新Cookie:
- 使用
redirects参数限制重定向次数,手动处理后续请求; - 登录完成后,提取响应中的所有Cookie更新到k6的Cookie Jar。
3. 模拟浏览器请求头
Liferay可能校验User-Agent、Referer等请求头字段,k6默认请求头与浏览器差异较大,易触发认证拦截。需为所有请求添加浏览器风格的请求头:
const browserHeaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Referer': 'http://localhost:8080/web/guest/home', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' };
4. 验证Cookie完整性
除JSESSIONID外,Liferay还依赖LFR_SESSION_STATE_*等系列Cookie。可登录后打印k6的Cookie Jar内容,对比浏览器Cookie列表,确保无遗漏:
console.log(JSON.stringify(http.cookieJar().getCookies('http://localhost:8080')));
调整后的完整k6测试脚本示例
import http from 'k6/http'; import { parseHTML } from 'k6/html'; export default function () { const username = 'test-user'; const password = 'test-pass'; const baseUrl = 'http://localhost:8080'; const browserHeaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Referer': `${baseUrl}/web/guest/home`, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' }; // 1. 访问首页获取初始p_auth const homeResponse = http.get(`${baseUrl}/`, { headers: browserHeaders }); const homeDoc = parseHTML(homeResponse.body); const initialCsrfToken = homeDoc.find('input[name="p_auth"]').first().attr('value'); // 2. 执行登录请求 const loginParams = { headers: { ...browserHeaders, 'Content-Type': 'application/x-www-form-urlencoded' }, redirects: 2 // 显式控制重定向次数 }; const loginResponse = http.post( `${baseUrl}/web/guest/home?p_p_id=com_liferay_login_web_portlet_LoginPortlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&_com_liferay_login_web_portlet_LoginPortlet_javax.portlet.action=%2Flogin%2Flogin&_com_liferay_login_web_portlet_LoginPortlet_mvcRenderCommandName=%2Flogin%2Flogin`, { _com_liferay_login_web_portlet_LoginPortlet_formDate: Date.now(), _com_liferay_login_web_portlet_LoginPortlet_saveLastPath: "false", _com_liferay_login_web_portlet_LoginPortlet_redirect: "", _com_liferay_login_web_portlet_LoginPortlet_doActionAfterLogin: "false", _com_liferay_login_web_portlet_LoginPortlet_login: username, _com_liferay_login_web_portlet_LoginPortlet_password: password, _com_liferay_login_web_portlet_LoginPortlet_checkboxNames: "rememberMe", p_auth: initialCsrfToken, }, loginParams ); // 3. 提取登录后的最新p_auth const loginDoc = parseHTML(loginResponse.body); const postLoginCsrfToken = loginDoc.find('input[name="p_auth"]').first().attr('value'); if (!postLoginCsrfToken) { throw new Error('Failed to retrieve post-login p_auth token'); } // 4. 请求JWT const jwtResponse = http.get( `${baseUrl}/api/jsonws/my-app.auth/create-token?p_auth=${postLoginCsrfToken}`, { headers: browserHeaders } ); // 验证结果 if (jwtResponse.status !== 200) { console.log(`JWT请求失败: ${jwtResponse.status}, 响应内容: ${jwtResponse.body}`); } else { console.log('成功获取JWT'); } }
额外注意事项
- 替换
getFormDate()为Liferay期望的毫秒级时间戳; - 若Liferay开启严格CSRF校验,所有非静态资源请求都需携带正确的
p_auth令牌; - 可通过
k6 run -v script.js开启请求日志,对比浏览器请求详情定位差异点。
内容的提问来源于stack exchange,提问作者shawmanz32na
相关产品推荐
相关产品推荐

