无需Node.js,JS客户端调用login.microsoftonline.com获取Bearer Token报错求助
问题根源与解决方案
为什么浏览器端调用会返回400?
client_credentials(客户端凭据)模式是服务间认证专用的,Azure AD明确禁止在浏览器端(纯前端JS)使用这个模式:
- 前端代码会直接暴露
client_secret,这属于严重的安全漏洞,任何人都能扒下来盗用你的应用权限。 - Azure AD的token端点默认拒绝浏览器发起的跨域预检请求(OPTIONS),因为这种模式设计时就没考虑前端场景,所以直接返回400。
Postman和浏览器调用的核心差异
- Postman是桌面/后端工具,不属于浏览器的跨域安全模型,也不会把
client_secret暴露给用户,符合Azure AD对client_credentials模式的使用场景要求。 - 浏览器端受同源策略限制,发起跨域请求时会先发送OPTIONS预检请求,而Azure AD的token端点不支持前端应用的预检请求,同时前端暴露
client_secret的行为也会被安全机制拦截。
你能用到的替代方案(无需Node.js)
放弃client_credentials模式,改用授权码模式+PKCE,这是微软推荐的纯前端应用认证方案,不需要client_secret,也不需要后端服务器:
步骤1:生成PKCE所需的验证参数
// 生成随机的code_verifier function generateCodeVerifier() { let array = new Uint32Array(56/2); window.crypto.getRandomValues(array); return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); } // 基于code_verifier生成code_challenge async function generateCodeChallenge(codeVerifier) { const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); const digest = await window.crypto.subtle.digest('SHA-256', data); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); }
步骤2:跳转到Azure AD授权页面获取授权码
const clientId = "你的client_id"; const tenantId = "你的tenant_id"; const redirectUri = "你的前端回调地址(需在Azure AD应用里配置)"; const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); // 把codeVerifier存在localStorage,后续请求token要用 localStorage.setItem('code_verifier', codeVerifier); // 构造授权URL并跳转 const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?` + new URLSearchParams({ client_id: clientId, response_type: 'code', redirect_uri: redirectUri, response_mode: 'query', scope: '你要访问的资源scope(比如https://graph.microsoft.com/.default)', code_challenge: codeChallenge, code_challenge_method: 'S256' }); window.location.href = authUrl;
步骤3:在回调页面用授权码请求Token
// 从URL中获取授权码 const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const codeVerifier = localStorage.getItem('code_verifier'); if (code && codeVerifier) { const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const formData = new URLSearchParams({ client_id: clientId, grant_type: 'authorization_code', code: code, redirect_uri: redirectUri, code_verifier: codeVerifier, scope: '你要访问的资源scope' }); fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData }) .then(res => res.json()) .then(data => { console.log('获取到的Token:', data.access_token); // 这里可以处理token,比如存在localStorage或sessionStorage }) .catch(err => console.error('请求Token失败:', err)); }
额外注意事项
- 必须在Azure AD应用注册里配置正确的
redirect_uri,否则会被拒绝。 - 确保你请求的
scope是Azure AD应用已经授权过的权限。 - 不要在前端代码里存放任何敏感信息(比如
client_secret),这是前端应用的基本安全原则。
内容的提问来源于stack exchange,提问作者Pascal Stephan
相关产品推荐
相关产品推荐

