You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

后端修复时遭遇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');
  }
});

相关截图

相关截图

错误原因分析

  1. 错误的Graph API端点:代码直接使用SharePoint浏览器页面URL作为请求路径,而Microsoft Graph API要求使用特定格式的端点(如/sites/{site-id}/drive/items/{item-id}/content),无法识别普通的SharePoint页面链接。
  2. 应用权限不足:使用ClientSecretCredential进行应用级认证时,Azure AD应用未配置足够的权限(如Files.Read.All、Sites.Read.All),或未完成管理员同意授权。
  3. 文件下载逻辑错误:当前代码请求的是文件元数据而非文件流,且存在_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:24:54