Chrome扩展declarativeNetRequest动态规则不更新,令牌过期致401问题
问题分析与解决方案
核心问题
首次运行时令牌可正常传递,但1小时access token过期后,新令牌无法更新到declarativeNetRequest规则中,导致API请求返回401错误。现有实现的主要问题:
webRequest.onBeforeRequest拦截请求时异步刷新令牌的时机太晚——请求已进入处理流程,无法及时更新规则修改当前请求的请求头- 缺少对令牌过期时间的主动监控,依赖请求触发刷新的方式无法覆盖所有场景
- 未确认
UpdateIntercept函数是否正确调用declarativeNetRequest.updateDynamicRules完成规则更新
解决方案
方案1:主动监控令牌过期,提前刷新并更新规则
这是最优方案,避免请求时临时处理的延迟问题:
存储令牌与过期时间
从JWT中解析过期时间,或利用认证响应中的expires_in字段计算,同时存储refresh_token用于刷新:// 解码JWT获取过期时间(毫秒级) function getTokenExpiry(token: string): number { try { const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000; } catch (e) { return 0; } } // 获取令牌后初始化存储与刷新调度 chrome.identity.launchWebAuthFlow( { url: azureTokenAuthUrl, interactive: isInteractive }, async (redirectURL: string) => { if (!redirectURL) return; // 注意:Azure认证响应的参数在hash中,而非query const params = new URLSearchParams(redirectURL.split('#')[1]); const accessToken = params.get("access_token") || ''; const refreshToken = params.get("refresh_token") || ''; // 存储令牌到本地 await chrome.storage.local.set({ accessToken, refreshToken }); // 更新拦截规则 await UpdateIntercept(accessToken); // 调度过期前自动刷新 scheduleTokenRefresh(accessToken); callback(accessToken); } );自动刷新令牌并更新规则
在令牌过期前5分钟触发刷新,更新规则后继续调度下一次刷新:async function scheduleTokenRefresh(token: string) { const expiryTime = getTokenExpiry(token); const refreshTime = expiryTime - 5 * 60 * 1000; // 过期前5分钟刷新 const now = Date.now(); if (refreshTime > now) { setTimeout(async () => { const newToken = await refreshAccessToken(); if (newToken) { await UpdateIntercept(newToken); scheduleTokenRefresh(newToken); } else { // 刷新失败,触发重新登录 chrome.runtime.sendMessage({ message: "get_token" }); } }, refreshTime - now); } } // 用refresh_token获取新access_token的实现 async function refreshAccessToken(): Promise<string | null> { const { refreshToken } = await chrome.storage.local.get("refreshToken"); if (!refreshToken) return null; try { const response = await fetch(azureRefreshTokenUrl, { method: 'POST', body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: yourClientId }) }); const data = await response.json(); await chrome.storage.local.set({ accessToken: data.access_token }); return data.access_token; } catch (e) { console.error("刷新令牌失败", e); return null; } }完善规则更新函数
确保正确替换旧规则:async function UpdateIntercept(newToken: string) { const rules = GetInterceptRules(newToken); await chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: [1, 2], addRules: rules }); }
方案2:用webRequest直接修改请求头(备选)
如果不想用declarativeNetRequest,可以直接在请求发送前修改头,需要添加webRequestBlocking权限:
async function modifyAuthHeader(details: chrome.webRequest.WebRequestHeadersDetails) { const { accessToken } = await chrome.storage.local.get("accessToken"); if (accessToken) { // 移除旧的Authorization头,添加新的 details.requestHeaders = details.requestHeaders?.filter(h => h.name.toLowerCase() !== 'authorization') || []; details.requestHeaders.push({ name: 'Authorization', value: `Bearer ${accessToken}` }); return { requestHeaders: details.requestHeaders }; } chrome.runtime.sendMessage({ message: "get_token" }); return { requestHeaders: details.requestHeaders }; } chrome.webRequest.onBeforeSendHeaders.addListener( modifyAuthHeader, { urls: [liveApiUrl, testApiUrl], initiatorDomains: ["mail.google.com"] }, ["requestHeaders", "blocking"] );
Manifest V3权限调整
如果使用方案2,需要在权限中添加:
"permissions": [ "webRequest", "webRequestBlocking", "declarativeNetRequest", "declarativeNetRequestWithHostAccess", "identity", "identity.email", "storage" ]
内容的提问来源于stack exchange,提问作者Matheus Irineu
相关产品推荐
相关产品推荐

