配置OneDrive FilePicker v8 for Business/SharePoint遇受众URI验证失败异常
现有配置信息
AAD应用配置
{ "id": "***", "acceptMappedClaims": null, "accessTokenAcceptedVersion": null, "addIns": [], "allowPublicClient": null, "appId": "***", "appRoles": [], "oauth2AllowUrlPathMatching": false, "createdDateTime": "2023-01-17T13:20:32Z", "description": null, "certification": null, "disabledByMicrosoftStatus": null, "groupMembershipClaims": null, "identifierUris": [], "informationalUrls": { "termsOfService": "https://***.com/app/tos", "support": null, "privacy": "https://***.com/app/privacy", "marketing": null }, "keyCredentials": [], "knownClientApplications": [], "logoUrl": "https://aadcdn.msftauthimages.net/c1c6b6c8-v-6zmda-jxeuzcj5pwn1sghjyu3gaqcdbwxfx543nak/appbranding/caofuabptruma-iurozspazfz7p4ilc95fi3qyhjlu0/1033/bannerlogo?ts=638095585782399340", "logoutUrl": null, "name": "File Picker (SharePoint)", "notes": null, "oauth2AllowIdTokenImplicitFlow": false, "oauth2AllowImplicitFlow": false, "oauth2Permissions": [], "oauth2RequirePostResponse": false, "optionalClaims": null, "orgRestrictions": [], "parentalControlSettings": { "countriesBlockedForMinors": [], "legalAgeGroupRule": "Allow" }, "passwordCredentials": [], "preAuthorizedApplications": [], "publisherDomain": "***.com", "replyUrlsWithType": [ { "url": "https://localhost", "type": "Spa" }, { "url": "http://localhost", "type": "Spa" }, { "url": "https://***.com/onedrive/search", "type": "Spa" } ], "requiredResourceAccess": [<see next image>], "samlMetadataUrl": null, "signInUrl": "https://***.com", "signInAudience": "AzureADMultipleOrgs", "tags": [], "tokenEncryptionKeyId": null }
AAD权限范围

浏览器端实现代码
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="/public/css/style.css"> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"> <title>File Picker</title> </head> <body> <div class="container-lg pt-3"> <h3 class="d-flex align-items-center"><span>OneDrive File Picker</span></h3> <div><button class="btn btn-primary" id="launchPicker">Pick from OneDrive</button></div> <iframe class="w-100 mt-3" id="iframe" frameborder="0" style="min-height:600px;resize:vertical;"></iframe> </div> <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.19.0/js/msal-browser.min.js" nonce="392b4bdf6481a24edf09d81187872ce7"></script> <script type="text/javascript" nonce="392b4bdf6481a24edf09d81187872ce7"> const SUB = "***"; // Test user const REDIRECT_URI = `https://***.com/onedrive/search` // Test redirect url const BASE_URL = `https://***-my.sharepoint.com/` // Test url const msalParams = { auth: { authority: "https://login.microsoftonline.com/organizations", // Full directory URL, in the form of https://login.microsoftonline.com/<tenant> clientId: "***", // 'Application (client) ID' of app registration in Azure portal - this value is a GUID redirectUri: REDIRECT_URI }, } const app = new msal.PublicClientApplication(msalParams); const redirectResponse = app.handleRedirectPromise(); async function getToken() { let accessToken = ""; const account = await app.getAccountByLocalId(SUB); if (await redirectResponse !== null) { // Acquire token silent success accessToken = redirectResponse.accessToken; } else { authParams = { scopes: [`${BASE_URL}.default`], account: account }; try { // see if we have already the idtoken saved const resp = await app.acquireTokenSilent(authParams); accessToken = resp.accessToken; } catch (e) { // per examples we fall back to popup return app.acquireTokenRedirect(authParams); } } return accessToken; } // the options we pass to the picker page through the querystring const params = { sdk: "8.0", entry: { oneDrive: { files: {}, } }, authentication: {}, messaging: { origin: window.location.href, channelId: "27" }, typesAndSources: { mode: "files", pivots: { oneDrive: true, recent: true, sharedLibraries: true, }, //- filters: ".csv,.xlsx" }, selection: { mode: "pick" }, commands: { pick: { label: "Import" }, close: { label: "Close" } } }; async function launchPicker(e) { e.preventDefault(); //- win = window.open("", "Picker", "width=800,height=600") const frame = document.getElementById("iframe"); const win = frame.contentWindow; const authToken = await getToken(); const queryString = new URLSearchParams( { filePicker: JSON.stringify(params), }); const url = `${BASE_URL}_layouts/15/FilePicker.aspx?${queryString}`; const form = win.document.createElement("form"); form.setAttribute("action", url); form.setAttribute("method", "POST"); win.document.body.append(form); const input = win.document.createElement("input"); input.setAttribute("type", "hidden") input.setAttribute("name", "access_token"); input.setAttribute("value", authToken); form.appendChild(input); form.submit(); window.addEventListener("message", (event) => { if (event.source && event.source === win) { const message = event.data; if (message.type === "initialize" && message.channelId === params.messaging.channelId) { port = event.ports[0]; port.addEventListener("message", messageListener); port.start(); port.postMessage( { type: "activate", }); } } }); } async function messageListener(message) { switch (message.data.type) { case "notification": console.log(`notification: ${message.data}`); break; case "command": port.postMessage( { type: "acknowledge", id: message.data.id, }); const command = message.data.data; switch (command.command) { case "authenticate": // getToken is from scripts/auth.js const token = await getToken(); if (typeof token !== "undefined" && token !== null) { port.postMessage( { type: "result", id: message.data.id, data: { result: "token", token, } }); } else { console.error(`Could not get auth token for command: ${JSON.stringify(command)}`); } break; case "close": history.back(); break; case "pick": console.log(`Picked: ${JSON.stringify(command)}`); port.postMessage( { type: "result", id: message.data.id, data: { result: "success", }, }); console.info(command) break; default: console.warn(`Unsupported command: ${JSON.stringify(command)}`, 2); port.postMessage( { result: "error", error: { code: "unsupportedCommand", message: command.command }, isExpected: true, }); break; } break; } } document.getElementById("launchPicker").onclick = launchPicker; // Check the token directly and reload when no access token acquirable getToken().then((accessToken) => { if (!accessToken) { console.info("Something went wrong acquiring a token, reloading the page and try again!") window.location.reload(); } }) </script> <footer></footer> </body> </html>
解决方案
核心问题定位
Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException本质是令牌的受众(Audience)与SharePoint验证时期望的受众不匹配,结合你的配置,主要问题集中在AAD应用配置和令牌请求逻辑。
1. AAD应用配置修正
- 添加标识符URI:当前配置中
identifierUris为空,企业环境下SharePoint需要应用有明确的标识符URI才能验证令牌。操作步骤:- 进入Azure AD应用注册→概述→点击「添加标识符URI」
- 输入格式如
api://<你的客户端ID>或符合企业域名的URI,保存后identifierUris会生成有效值
- 补全SharePoint权限:从权限截图确认已添加SharePoint Online的Delegated权限(如
Sites.Read.All、Files.Read.All),并完成管理员同意(租户级应用需此步骤)
2. JavaScript代码调整
- 修改令牌请求作用域:将原
${BASE_URL}.default替换为Microsoft Graph统一作用域,确保覆盖SharePoint站点访问权限:// 替换原authParams中的scopes scopes: ["https://graph.microsoft.com/Sites.Read.All", "https://graph.microsoft.com/Files.Read.All"] - 验证令牌受众:获取令牌后,用本地JWT解码工具查看
aud字段,确认其为https://graph.microsoft.com(使用Graph作用域时)或正确的SharePoint租户受众 - FilePicker参数优化:确保
typesAndSources.pivots.sharedLibraries为true的同时,可尝试将FilePicker切换为Microsoft Graph驱动模式(更适配企业多站点场景)
3. SharePoint权限验证
- 确认测试用户拥有目标SharePoint站点/文件夹的至少读取权限
- 检查站点是否设置了严格的访问控制(如仅限特定安全组),确保测试用户在允许范围内
验证步骤
- 完成AAD应用标识符URI配置并保存
- 更新代码中的令牌请求作用域
- 重新获取令牌并验证受众正确性
- 重启应用测试SharePoint文件夹访问
内容的提问来源于stack exchange,提问作者Miaucl
相关产品推荐
相关产品推荐

