后端修复时遭遇GraphError(403禁止)问题求助
排查Microsoft Graph API 403 FORBIDDEN错误
报错信息
backend-1 | GraphError backend-1 | at new GraphError (/app/node_modules/@microsoft/microsoft-graph-client/lib/src/GraphError.js:34:28) backend-1 | at Function. (/app/node_modules/@microsoft/microsoft-graph-client/lib/src/GraphErrorHandler.js:97:30) backend-1 | at step (/app/node_modules/tslib/tslib.js:195:27) backend-1 | at Object.next (/app/node_modules/tslib/tslib.js:176:57) backend-1 | at /app/node_modules/tslib/tslib.js:169:75 backend-1 | at new Promise () backend-1 | at Object.__awaiter (/app/node_modules/tslib/tslib.js:165:16) backend-1 | at GraphErrorHandler.getError (/app/node_modules/@microsoft/microsoft-graph-client/lib/src/GraphErrorHandler.js:87:24) backend-1 | at GraphRequest. (/app/node_modules/@microsoft/microsoft-graph-client/lib/src/GraphRequest.js:315:84) backend-1 | at step (/app/node_modules/tslib/tslib.js:195:27) { backend-1 | statusCode: 403, backend-1 | code: null, backend-1 | requestId: null, backend-1 | date: 2024-11-27T10:18:11.306Z, backend-1 | body: '403 FORBIDDEN' backend-1 | }
相关代码
router.post('/login', (req, res) => { //check if password match // retlogurn jwt token //bcrpt compare password const { errors, isValid } = validateLoginInput(req.body); if (!isValid) { return res.status(400).json(errors); } const clientId = '...'; const clientSecret = '...'; const tenantId = '...'; const excelFileId = 'https://vincil.sharepoint.com/:x:/r/sites/reservoir/_layouts/15/Doc2.aspx?action=edit&sourcedoc=%7Bca3eb7d8-98e3-4e33-ba33-a4fb77d0cfc3%7D&wdOrigin=TEAMS-WEB.teamsSdk_ns.rwc&wdExp=TEAMS-TREATMENT&wdhostclicktime=1732623964116&web=1'; const credential = new ClientSecretCredential( tenantId, clientId, clientSecret ); async function getAccessToken() { const tokenResponse = await credential.getToken( 'https://graph.microsoft.com/.default' ); console.log(tokenResponse); return tokenResponse.token; } async function getGraphClient() { const accessToken = await getAccessToken(); return Client.init({ authProvider: (done) => { done(null, accessToken); }, }); } async function downloadExcelFile() { const client = await getGraphClient(); const fileDownloadPath = excelFileId; const fileData = await client.api(fileDownloadPath).get(); const savePath = path.join(_dirname, 'uploads', 'teams-file.xlsx'); const writeStream = fs.createWriteStream(savePath); fileData.pipe(writeStream); writeStream.on('finish', () => { console.log('Excel file saved successfully'); }); } downloadExcelFile().catch(console.error); async function comparePasswords(plaintextPassword, hashedPassword) { try { return await bcrypt.compare(plaintextPassword, hashedPassword); } catch (error) { console.error('Error comparing passwords:', error); return false; } } try { User.findOne({ email: req.body.email, }).then(async (user) => { if (user) { const isMatch = await comparePasswords( req.body.password.toString(), user.password.toString() ); console.log(isMatch); if (isMatch) { res.json({ id: user.id, name: user.name, email: user.email, token: generateToken(user.id), }); // res.send(null); } else { res.status(400).json({ invalidCredentials: 'Invalid Credentials' }); } } else { res.send(null); } }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } });
相关截图

错误原因分析
- 错误的Graph API端点:代码直接使用SharePoint浏览器页面URL作为请求路径,而Microsoft Graph API要求使用特定格式的端点(如
/sites/{site-id}/drive/items/{item-id}/content),无法识别普通的SharePoint页面链接。 - 应用权限不足:使用
ClientSecretCredential进行应用级认证时,Azure AD应用未配置足够的权限(如Files.Read.All、Sites.Read.All),或未完成管理员同意授权。 - 文件下载逻辑错误:当前代码请求的是文件元数据而非文件流,且存在
_dirname笔误(应为__dirname)。
解决方法
1. 修正Graph API请求路径
从SharePoint文件URL中提取sourcedoc对应的文件ID(即ca3eb7d8-98e3-4e33-ba33-a4fb77d0cfc3),同时获取站点ID(可通过Graph API /sites/vincil.sharepoint.com:/sites/reservoir 查询),构造正确的文件内容端点:
const siteId = "你的站点ID"; // 示例:sites/reservoir对应的ID const fileItemId = "ca3eb7d8-98e3-4e33-ba33-a4fb77d0cfc3"; const fileDownloadPath = `/sites/${siteId}/drive/items/${fileItemId}/content`;
2. 配置并授权应用权限
- 登录Azure门户,找到目标应用注册。
- 进入API权限页面,添加Microsoft Graph的应用权限:
Files.Read.All或Sites.Read.All(根据业务需求选择)。 - 点击授予管理员同意,确保权限生效。
3. 修正文件下载逻辑
调整代码以获取文件流,并修正路径笔误:
async function downloadExcelFile() { const client = await getGraphClient(); const siteId = "你的站点ID"; const fileItemId = "ca3eb7d8-98e3-4e33-ba33-a4fb77d0cfc3"; const fileDownloadPath = `/sites/${siteId}/drive/items/${fileItemId}/content`; // 指定响应类型为流 const fileStream = await client.api(fileDownloadPath).get({ responseType: 'stream' }); const savePath = path.join(__dirname, 'uploads', 'teams-file.xlsx'); const writeStream = fs.createWriteStream(savePath); fileStream.pipe(writeStream); writeStream.on('finish', () => { console.log('Excel file saved successfully'); }); writeStream.on('error', (err) => { console.error('Error saving file:', err); }); }
内容的提问来源于stack exchange,提问作者Mitema Emmanuel
相关产品推荐
相关产品推荐

