服务端阅读 05月27日 23:12
如何对 JWT 进行测试
JWT 测试需要从三个层面入手:单元测试验证签名和解析逻辑,集成测试验证完整认证流程,安全测试验证防护常见攻击。下面逐层展开。单元测试:Token 生成与验证单元测试关注 JWT 库本身的行为是否正确。核心测试点有三个:生成出的 token 结构是否合法、payload 是否正确写入、过期时间是否生效。Token 生成测试const jwt = require('jsonwebtoken');const { expect } = require('chai');describe('JWT Token Generation', () => { const SECRET = 'test-secret'; const payload = { userId: '123', role: 'user' }; it('should generate a valid three-part token', () => { const token = jwt.sign(payload, SECRET); expect(token.split('.')).to.have.lengthOf(3); }); it('should encode payload correctly', () => { const decoded = jwt.decode(jwt.sign(payload, SECRET)); expect(decoded.userId).to.equal('123'); expect(decoded.role).to.equal('user'); }); it('should set expiration', () => { const decoded = jwt.decode(jwt.sign(payload, SECRET, { expiresIn: '1h' })); expect(decoded.exp).to.be.greaterThan(Math.floor(Date.now() / 1000)); });});Token 验证测试验证环节容易出问题,重点测三类异常:签名不匹配、token 过期、算法不符。describe('JWT Token Verification', () => { const SECRET = 'test-secret'; it('should verify a valid token', () => { const token = jwt.sign({ userId: '123' }, SECRET); const decoded = jwt.verify(token, SECRET); expect(decoded.userId).to.equal('123'); }); it('should reject expired token', () => { const expired = jwt.sign({ userId: '123' }, SECRET, { expiresIn: '-1s' }); expect(() => jwt.verify(expired, SECRET)).to.throw('jwt expired'); }); it('should reject wrong secret', () => { const token = jwt.sign({ userId: '123' }, SECRET); expect(() => jwt.verify(token, 'wrong')).to.throw('invalid signature'); }); it('should reject wrong algorithm', () => { const token = jwt.sign({ userId: '123' }, SECRET, { algorithm: 'HS256' }); expect(() => jwt.verify(token, SECRET, { algorithms: ['RS256'] })) .to.throw('invalid algorithm'); });});注意 algorithms 白名单是必须的——后面安全测试会解释原因。集成测试:认证流程单元测试只覆盖了库函数,集成测试验证 token 在 HTTP 请求中的真实表现。关键场景:登录拿 token → 带 token 访问受保护资源 → token 无效/过期时被拒绝。const request = require('supertest');const app = require('../app');describe('Auth Flow', () => { let token; it('should return token on login', async () => { const res = await request(app) .post('/auth/login') .send({ username: 'testuser', password: 'password123' }); expect(res.status).to.equal(200); expect(res.body.token).to.exist; token = res.body.token; }); it('should access protected route with valid token', async () => { const res = await request(app) .get('/api/protected') .set('Authorization', `Bearer ${token}`); expect(res.status).to.equal(200); }); it('should reject request without token', async () => { const res = await request(app) .get('/api/protected'); expect(res.status).to.equal(401); }); it('should reject expired token', async () => { const expired = jwt.sign({ userId: '123' }, process.env.JWT_SECRET, { expiresIn: '-1s' }); const res = await request(app) .get('/api/protected') .set('Authorization', `Bearer ${expired}`); expect(res.status).to.equal(401); });});如果系统使用 refresh token,还需要单独测试 refresh 端点:用合法 refresh token 换新 access token,用无效 refresh token 被拒绝。安全测试:防护常见攻击JWT 最常见的安全问题有两个:算法混淆攻击和 token 篡改。算法混淆攻击攻击者把 header 中的 alg 改为 none,绕过签名验证。防御方式是在 verify 时显式指定 algorithms 白名单:describe('Security - Algorithm Confusion', () => { it('should reject none algorithm', () => { const noneToken = jwt.sign({ userId: 'admin' }, '', { algorithm: 'none' }); expect(() => jwt.verify(noneToken, SECRET, { algorithms: ['HS256'] })) .to.throw(); });});这就是为什么前面单元测试中强调 algorithms 白名单——不写白名单的 verify 调用就是安全漏洞。Token 篡改攻击者修改 payload 后重新拼接 token,但无法生成合法签名,所以 verify 必须能检测到签名不匹配:describe('Security - Token Tampering', () => { it('should reject tampered payload', () => { const token = jwt.sign({ userId: '123' }, SECRET); const parts = token.split('.'); parts[1] = Buffer.from(JSON.stringify({ userId: 'admin' })).toString('base64'); const tampered = parts.join('.'); expect(() => jwt.verify(tampered, SECRET)).to.throw('invalid signature'); });});边界情况补充除了上述三层,实际项目中还需关注:时钟偏移:分布式系统中各节点时钟不同步,verify 时设置 clockTolerance(如 30 秒)避免误判过期。密钥轮换:旧密钥签发的 token 在新密钥上线后仍需能验证,需支持多密钥验证逻辑。并发刷新:多个请求同时触发 token 刷新,需要幂等处理避免竞态条件。追问JWT 的 alg: none 攻击原理是什么?如何防御?RS256 和 HS256 在验证时有什么安全差异?(提示:RS256 公钥验证 vs HS256 共享密钥,HS256 存在密钥泄露风险)如何在不停机的情况下轮换 JWT 签名密钥?