import { test, expect } from '@playwright/test'; test.describe('前后端加密通信验证', () => { test.use({ storageState: { cookies: [], origins: [] } }); test('登录请求应被加密(请求体非明文JSON,响应体非明文JSON)', async ({ page }) => { // 用于捕获登录 API 请求和响应的变量 let capturedRequestUrl: string | null = null; let capturedRequestBody: string | null = null; let capturedRequestHeaders: Record = {}; let capturedResponseStatus: number | null = null; let capturedResponseBody: string | null = null; let capturedResponseHeaders: Record = {}; // 监听 API 请求——在请求发送前捕获 page.on('request', request => { if (request.url().includes('/api/auth/login') && request.method() === 'POST') { capturedRequestUrl = request.url(); capturedRequestHeaders = request.headers() as Record; // 对于 POST 请求,Playwright 无法直接获取 body,通过 route 拦截 } }); // 使用 route 拦截请求体 await page.route('**/api/auth/login', async (route) => { const postData = route.request().postData(); if (postData) { capturedRequestBody = postData; } // 继续请求,但拦截响应 const response = await route.fetch(); capturedResponseStatus = response.status(); capturedResponseHeaders = response.headers() as Record; capturedResponseBody = await response.text(); // 继续原始响应 await route.fulfill({ response }); }); // 导航到登录页 await page.goto('/login'); await page.waitForLoadState('networkidle'); // 填入登录信息 await page.fill('input[type="text"], input[placeholder*="用户名"]', 'admin'); await page.fill('input[type="password"], input[placeholder*="密码"]', 'Test@123'); // 点击登录 await page.click('button:has-text("登录")'); // 等待登录完成 await page.waitForURL(/.*dashboard/, { timeout: 30000 }); // ========== 验证请求体加密 ========== await test.step('验证请求体已加密(非明文JSON)', async () => { expect(capturedRequestBody).toBeTruthy(); // 请求体不应是明文 JSON(不应包含 username 字段) expect(capturedRequestBody).not.toContain('"username"'); expect(capturedRequestBody).not.toContain('"admin"'); expect(capturedRequestBody).not.toContain('"password"'); // 请求体应为 Base64 编码(AES-GCM 密文) expect(capturedRequestBody).toMatch(/^[A-Za-z0-9+/=]+$/); }); await test.step('验证 X-Encrypted 请求头存在', async () => { expect(capturedRequestHeaders['x-encrypted']?.toLowerCase()).toBe('true'); }); // ========== 验证响应体加密 ========== await test.step('验证响应体已加密(非明文JSON)', async () => { expect(capturedResponseBody).toBeTruthy(); expect(capturedResponseStatus).toBe(200); // 响应体不应是明文 JSON(不应包含 token 字段) expect(capturedResponseBody).not.toContain('"token"'); // 响应体应为 Base64 编码 expect(capturedResponseBody).toMatch(/^[A-Za-z0-9+/=]+$/); }); await test.step('验证 X-Encrypted 响应头存在', async () => { expect(capturedResponseHeaders['x-encrypted']?.toLowerCase()).toBe('true'); }); // ========== 验证业务正常 ========== await test.step('验证登录成功——页面跳转到 dashboard', async () => { await expect(page).toHaveURL(/.*dashboard/); }); }); test('GET 请求的响应体也应被加密', async ({ page }) => { // 先登录 await page.goto('/login'); await page.waitForLoadState('networkidle'); await page.fill('input[type="text"], input[placeholder*="用户名"]', 'admin'); await page.fill('input[type="password"], input[placeholder*="密码"]', 'Test@123'); await page.click('button:has-text("登录")'); await page.waitForURL(/.*dashboard/, { timeout: 30000 }); // 捕获 GET 请求 let capturedGetBody: string | null = null; let capturedGetEncrypted: string | null = null; await page.route('**/api/users**', async (route) => { // 仅拦截 GET 请求 if (route.request().method() === 'GET') { const response = await route.fetch(); capturedGetEncrypted = response.headers()['x-encrypted'] ?? null; capturedGetBody = await response.text(); await route.fulfill({ response }); } else { await route.continue(); } }); // 导航到用户管理页面触发 GET 请求 await page.goto('/users'); await page.waitForLoadState('networkidle'); await test.step('验证 GET 响应已加密', async () => { // 注意:如果用户无权限访问 /users,可能没有加密响应 // 这里只验证如果响应存在,且标记了加密,则响应体应为 Base64 if (capturedGetEncrypted) { expect(capturedGetEncrypted).toBe('true'); expect(capturedGetBody).toMatch(/^[A-Za-z0-9+/=]+$/); expect(capturedGetBody).not.toContain('"id"'); } }); }); });