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

使用JavaScript获取Azure OAuth2令牌访问O365服务通信API遇阻求助

解决Office 365 Service Communications API浏览器端调用的CORS问题

我太懂你现在的头疼了——想给同事搭个页面看O365服务状态,已经搞定Azure AD应用注册,Postman和PowerShell调用都顺得很,结果一用浏览器JavaScript调用就碰CORS错误,试了MSAL.js也没解决,还对官方说的“禁用浏览器CORS”的提示一脸懵。咱们来一步步把这个问题捋清楚。

核心问题:API本身不支持浏览器端CORS

首先得明确:Office 365 Service Communications API并没有针对浏览器端配置CORS允许规则。这就是为什么Postman/PowerShell这类非浏览器工具能正常调用——它们不受浏览器同源策略的限制;而浏览器里的JS代码会因为同源策略被拦截,报CORS错误。

官方文档里提到的“禁用浏览器CORS”只是给本地测试用的临时方案(比如用Chrome的--disable-web-security参数),绝对不能用于给同事使用的生产环境,这完全是不安全的做法。

你的代码里的几个明显问题

先不说CORS的事,你的MSAL.js代码本身有几个逻辑错误,导致就算CORS没问题也拿不到正确的数据:

  1. 重复引入了msal.js和msal.min.js,只需要保留其中一个就行
  2. Demo变量没有赋值,你需要把获取到的accessToken赋值给它才能在请求头里带上
  3. 请求的scopes不对——你要调用的是Service Communications API,需要的权限是https://manage.office.com/ServiceHealth.Read,而不是user.read和mail.send
  4. fetch请求在token获取之前就执行了,根本没带上有效的身份凭证

修正后的前端代码(仍受CORS限制,仅作逻辑参考)

先把代码里的逻辑错误修正,这样至少在解决CORS问题后能正常工作:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>O365 Service Status</title>
    <!-- 只保留一个MSAL库引入 -->
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.0/js/msal.min.js"></script>
</head>
<body>
    <script type="text/javascript">
        const msalConfig = {
            auth: {
                clientId: '2076e785-XXXX-XXXX-XXXX-XXXXXXXXXX',
                authority: 'https://login.microsoftonline.com/contoso.onmicrosoft.com' // 必须指定租户ID或域名
            }
        };

        const msalInstance = new Msal.UserAgentApplication(msalConfig);

        // 处理登录回调
        msalInstance.handleRedirectCallback((error, response) => {
            if (error) {
                console.error('登录回调出错:', error);
                return;
            }
            if (response) {
                console.log('登录成功:', response);
                fetchServiceStatus(response.accessToken);
            }
        });

        // 定义API需要的权限
        const tokenRequest = {
            scopes: ["https://manage.office.com/ServiceHealth.Read"]
        };

        // 检查用户是否已登录
        if (msalInstance.getAccount()) {
            // 静默获取token
            msalInstance.acquireTokenSilent(tokenRequest)
                .then(response => {
                    fetchServiceStatus(response.accessToken);
                })
                .catch(err => {
                    // 需要交互获取token
                    if (err.name === "InteractionRequiredAuthError") {
                        msalInstance.acquireTokenPopup(tokenRequest)
                            .then(response => {
                                fetchServiceStatus(response.accessToken);
                            })
                            .catch(err => {
                                console.error('获取token失败:', err);
                            });
                    } else {
                        console.error('静默获取token出错:', err);
                    }
                });
        } else {
            // 用户未登录,弹出登录窗口
            msalInstance.loginPopup(tokenRequest)
                .then(response => {
                    fetchServiceStatus(response.accessToken);
                })
                .catch(err => {
                    console.error('登录失败:', err);
                });
        }

        // 调用Service Communications API
        function fetchServiceStatus(accessToken) {
            const graphEndpoint = "https://manage.office.com/api/v1.0/contoso.onmicrosoft.com/ServiceComms/Messages";
            const options = {
                method: "GET",
                headers: {
                    "Authorization": `Bearer ${accessToken}`
                }
            };

            fetch(graphEndpoint, options)
                .then(resp => {
                    if (!resp.ok) {
                        throw new Error(`API请求失败: ${resp.statusText}`);
                    }
                    return resp.json();
                })
                .then(data => {
                    console.log('服务状态数据:', data);
                    // 在这里把数据渲染到页面上
                })
                .catch(err => {
                    console.error('获取服务状态失败:', err);
                    // 这里还是会碰到CORS错误,因为API不支持浏览器端调用
                });
        }
    </script>
</body>
</html>

生产环境的正确解决方案:后端代理

既然浏览器端直接调用会碰CORS问题,那正确的做法是搭建一个后端代理服务:

  1. 浏览器端JS调用你自己的后端API(比如Node.js、Azure Functions、ASP.NET等)
  2. 后端服务用MSAL的后端库(比如msal-node)获取Office 365 API的访问token
  3. 后端服务调用Office 365 Service Communications API,拿到数据后返回给浏览器
  4. 浏览器渲染返回的数据给用户

这种方式完全绕开了浏览器的同源策略限制,而且更安全——token不会暴露在浏览器端。

举个简单的Node.js代理示例(用Express和msal-node):

const express = require('express');
const msal = require('@azure/msal-node');
const app = express();
const port = 3000;

const msalConfig = {
    auth: {
        clientId: '你的Azure AD应用ID',
        clientSecret: '你的Azure AD应用密钥',
        authority: 'https://login.microsoftonline.com/contoso.onmicrosoft.com'
    }
};

const cca = new msal.ConfidentialClientApplication(msalConfig);

const tokenRequest = {
    scopes: ["https://manage.office.com/.default"], // 后端用应用权限,需要在Azure AD里给应用分配Service Health Read权限
};

app.get('/api/o365-service-status', async (req, res) => {
    try {
        // 获取token
        const response = await cca.acquireTokenByClientCredential(tokenRequest);
        // 调用Office 365 API
        const apiResponse = await fetch('https://manage.office.com/api/v1.0/contoso.onmicrosoft.com/ServiceComms/Messages', {
            headers: {
                'Authorization': `Bearer ${response.accessToken}`
            }
        });
        const data = await apiResponse.json();
        res.json(data);
    } catch (err) {
        console.error('代理请求出错:', err);
        res.status(500).send('获取服务状态失败');
    }
});

app.listen(port, () => {
    console.log(`代理服务运行在 http://localhost:${port}`);
});

然后浏览器端只需要调用你自己的代理API:

fetch('http://localhost:3000/api/o365-service-status')
    .then(resp => resp.json())
    .then(data => {
        console.log('服务状态数据:', data);
        // 渲染到页面
    })
    .catch(err => console.error('请求出错:', err));

这样就完全解决了CORS问题,而且符合安全规范。

内容的提问来源于stack exchange,提问作者LonBoy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 00:27:46