从Azure AD获取Refresh Token时遇AADSTS9002313错误求助
错误详情
data {"error":"invalid_grant","error_description":"AADSTS9002313: 请求无效。请求格式不正确或无效。\r\nTrace ID: 4bcb6e5e-35c1-4c4e-b184-d0ffddcc6301\r\nCorrelation ID: 689f7abd-13ef-41f9-b94a-4b2269bb7c32\r\nTimestamp: 2023-03-03 11:15:39Z","error_codes":[9002313],"timestamp":"2023-03-03 11:15:39Z","trace_id":"4bcb6e5e-35c1-4c4e-b184-d0ffddcc6301","correlation_id":"689f7abd-13ef-41f9-b94a-4b2269bb7c32","error_uri":"https://login.microsoftonline.com/error?code=9002313"}
代码问题及修复方案
1. Token端点未启用
代码中tokenEndpoint被注释,导致请求无有效目标地址。取消注释并选择对应端点:
- 多租户应用用通用端点:
https://login.microsoftonline.com/common/oauth2/v2.0/token - 单租户应用用特定租户端点:
https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/token
2. 异步逻辑顺序错误
res.end(refreshToken)在fetch请求完成前就执行,此时refreshToken未赋值,还会提前终止响应。需把响应逻辑移到fetch的then回调内。
3. 手动截取Code存在风险
直接字符串截取获取code参数,若参数顺序变化或含URL编码字符,会导致参数失效。改用url模块解析Query参数更可靠。
4. 请求参数未做URL编码
client_secret等参数若含特殊字符(如&、=),直接拼接会破坏请求格式,必须用encodeURIComponent对每个参数编码。
5. 额外校验项
- 确保Azure AD应用注册里的
redirect_uri和代码中完全一致(包括大小写、结尾斜杠) - Authorization Code是一次性的,重复使用会触发
invalid_grant错误,必须用全新的code
修正后的代码
const http = require('http'); const url = require('url'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); // 解析请求URL中的query参数 const queryParams = url.parse(req.url, true).query; const code = queryParams.code; if (!code) { res.end('缺少code参数'); return; } const tokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; const clientId = '你的客户端ID'; const redirectUri = 'http://localhost:3000/callback'; const grantType = 'authorization_code'; const clientSecret = '你的客户端密钥'; // 对所有参数进行URL编码,避免格式错误 const tokenRequestBody = [ `grant_type=${encodeURIComponent(grantType)}`, `code=${encodeURIComponent(code)}`, `redirect_uri=${encodeURIComponent(redirectUri)}`, `client_id=${encodeURIComponent(clientId)}`, `client_secret=${encodeURIComponent(clientSecret)}` ].join('&'); const tokenRequest = { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: tokenRequestBody }; fetch(tokenEndpoint, tokenRequest) .then(response => { if (!response.ok) { throw new Error(`HTTP错误状态: ${response.status}`); } return response.json(); }) .then(data => { if (data.error) { res.end(`错误: ${data.error_description}`); return; } const refreshToken = data.refresh_token; res.end(`Refresh Token获取成功: ${refreshToken}`); }) .catch(error => { res.end(`请求失败: ${error.message}`); }); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
内容的提问来源于stack exchange,提问作者ab.it.gcp

