Hapi.js中JWT授权无法实现,携带Token访问路由返回401错误求助
Hapi.js JWT授权401错误排查与修复
问题场景
在Hapi.js后端集成JWT认证策略后,注册接口能正常返回Token,但携带该Token访问默认需要授权的/路由时,始终返回401未授权错误。
服务端核心代码
const Hapi = require('@hapi/hapi'); const Jwt = require('@hapi/jwt') const init = async () => { const server = Hapi.Server({ port: 4545, host: 'localhost', "routes": { "cors": { "origin": ["*"], "headers": ["Accept", "Content-Type"], "additionalHeaders": ["X-Requested-With"] } } }); server.ext('onPreResponse', (request, h) => { const response = request.response; if (response.isBoom) { response.output.headers['Access-Control-Allow-Origin'] = 'https://hapi.dev'; response.output.headers['Access-Control-Allow-Header'] = '*'; } else { response.headers['Access-Control-Allow-Origin'] = 'https://hapi.dev'; response.headers['Access-Control-Allow-Headers'] = '*'; } return h.continue; }); await server.register(Jwt) server.auth.strategy('my_jwt_strategy', 'jwt', { keys: 'some_shared_secret', verify: { aud: 'urn:audience:test', iss: 'urn:issuer:test', sub: false, nbf: true, exp: true, maxAgeSec: 14400, timeSkewSec: 15 }, validate: (artifacts, request, h) => { console.log('Validation: Start'); console.log('Decoded JWT:', artifacts.decoded); console.log('Validation: End'); return { isValid: true }; } }); server.auth.default('my_jwt_strategy'); // 注册接口 server.route({ method: 'POST', path: '/register', handler: async (request, h) => { const { username, password } = request.payload; const token = Jwt.token.generate({ user: username }, 'some_shared_secret'); return { token }; }, options: { auth: false, cors: { origin: ['*'], headers: ['*'], credentials: true } } }); // 需要授权的根路由 server.route({ method: 'GET', path: '/', handler: (request, h) => { return 'Hello, World!'; } }); await server.start(); console.log('Server started on port 4545 !'); }; init();
客户端请求代码
注册请求:
await fetch('http://localhost:4545/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({username: 'balbalsbd', password: 'asdasdada'}) }) .then(res => res.ok ? res.json() : Promise.reject({ status: res.status })) .then(data => console.log('Registration successful:', data)) .catch(err => console.error('Registration failed:', err));
测试授权请求:
await fetch('http://localhost:4545/', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }) .then(res => res.ok ? res.json() : Promise.reject({ status: res.status })) .then(data => console.log('Response:', data)) .catch(err => console.error('Error:', err));
核心问题分析
- Token声明不匹配:JWT策略配置中开启了
aud(受众)和iss(签发者)验证,但生成Token时仅传入了user字段,缺少这两个必填声明,导致验证失败返回401。 - Token生成方法参数错误:
Jwt.token.generate的正确用法需要明确指定payload、签名选项(包括密钥、算法),原代码的参数格式不符合@hapi/jwt的要求。 - CORS配置冲突:全局
onPreResponse中硬编码了固定的Origin头,与注册接口的*配置冲突,可能导致浏览器拦截请求头。
修复方案
1. 修正Token生成逻辑
在登录和注册接口中,生成包含完整声明的Token:
// 替换原Token生成代码 const token = Jwt.token.generate( { user: username, aud: 'urn:audience:test', // 与策略中的aud配置完全匹配 iss: 'urn:issuer:test', // 与策略中的iss配置完全匹配 exp: Math.floor(Date.now() / 1000) + 14400 // 4小时过期,和maxAgeSec一致 }, { key: 'some_shared_secret', algorithm: 'HS256' }, // 明确指定算法和密钥 { noTimestamp: false } );
2. 可选:关闭不必要的验证
如果不需要验证aud和iss,可以直接在策略中关闭:
server.auth.strategy('my_jwt_strategy', 'jwt', { keys: 'some_shared_secret', verify: { aud: false, // 关闭受众验证 iss: false, // 关闭签发者验证 sub: false, nbf: true, exp: true, maxAgeSec: 14400, timeSkewSec: 15 }, validate: (artifacts, request, h) => { return { isValid: true }; } });
3. 统一CORS配置
移除onPreResponse中的硬编码CORS头,改用全局路由配置:
const server = Hapi.Server({ port: 4545, host: 'localhost', routes: { cors: { origin: ['*'], headers: ['Accept', 'Content-Type', 'Authorization'], // 加入Authorization头 additionalHeaders: ['X-Requested-With'] } } }); // 移除原有的server.ext('onPreResponse')代码
4. 验证修复效果
重启服务后,重新注册获取新Token,携带Token访问/路由,此时应正常返回Hello, World!,不再出现401错误。
内容的提问来源于stack exchange,提问作者Abubakr Shomirsaidov
相关产品推荐
相关产品推荐

