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

从SPFx WebPart调用AD保护的Azure Function时遇401未授权问题

从SPFx WebPart调用AD保护的Azure Function时401未授权问题排查与解决

问题背景

尝试从SharePoint WebPart调用由AD保护的Azure Function,始终收到401未授权错误。已按教程配置但仍失败,且Azure门户的身份验证配置项已更新,官方文档存在滞后。

已完成的配置步骤

  • 在本地创建默认Azure Function,设为匿名访问,返回字符串“Hello world”。
  • 部署本地函数至Azure门户的Function App实例。
  • 通过浏览器/PowerShell(Invoke-WebRequest)测试函数,运行正常,返回“Hello World”。
  • 为Azure Function配置身份验证与授权:
    • 身份提供商:Microsoft
    • 创建名为AuthenticationFunctionAppAZF的应用注册
    • 客户端密钥名称:MICROSOFT_PROVIDER_AUTHENTICATION_SECRET
    • 颁发者URL:https://login.microsoftonline.com/common/v2.0
    • 允许的令牌受众:8bdb826f-bbbc-4f6b-923a-63777a5c3550(应用注册客户端ID)
    • 客户端应用要求:仅允许来自自身应用的请求
    • 身份要求:允许来自任何身份的请求
    • 租户要求:仅允许来自指定租户的请求(使用自身租户ID)
  • 在Azure Function对应的应用注册中配置:
    • Web应用回调URL:https://oauth.pstmn.io/v1/browser-callback(Postman测试用)、https://authenticationfunctionappazf.azurewebsites.net/.auth/login/aad/callback
    • 允许令牌:允许ID令牌
    • 支持的账户类型:任何组织目录中的账户(多租户)
  • 配置API权限与公开API:
    • 修改应用ID URI为https://tenantname.onmicrosoft.com/ClientID_of_AzureAppRegistration_of_My_Function
    • 添加作用域:user_impersonation、access_as_user
  • 浏览器测试:登录Azure门户后调用函数URL可正常返回“Hello world!”。

SPFx WebPart调用代码

import * as React from 'react';
import { useState } from 'react';
import { AadHttpClient, IHttpClientOptions, HttpClientResponse } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

export interface IHelloUserProps {
        context: WebPartContext;
}

export interface IHelloWorld {
        hello: string;
}

const HelloUser: React.FC<IHelloUserProps> = ({ context }) => {
        const [hello, setHello] = useState<IHelloWorld | null>(null);

        const fetchUserData = () => {
                console.log('Fetching user data...');
                if (!context) {
                        console.error('Context is undefined');
                        return;
                }

                console.log('Context:', context);
                if (!context.aadHttpClientFactory) {
                        console.error('aadHttpClientFactory is undefined');
                        return;
                }

                console.log('aadHttpClientFactory:', context.aadHttpClientFactory);

                const options: IHttpClientOptions = {
                        headers: {
                                'Accept': 'application/json;odata.metadata=none'
                        }
                };
                context.aadHttpClientFactory
                        .getClient('https://artetechnology.onmicrosoft.com/8bdb826f-bbbc-4f6b-923a-63777a5c3550')
                        .then((client: AadHttpClient): void => {
                                console.log('AadHttpClient obtained:', client);
                                client
                                        .get('https://authenticationfunctionappazf.azurewebsites.net/api/AuthenticationFunctionAppAZF?', AadHttpClient.configurations.v1, options)
                                        .then((response: HttpClientResponse): Promise<any> => {
                                                console.log('Response received:', response);
                                                return response.json();
                                        })
                                        .then((user: any): void => {
                                                console.log('User data:', user);
                                                setHello(user);
                                        })
                                        .catch((error: any): void => {
                                                console.error('Error parsing response:', error);
                                        });
                        })
                        .catch((error: any): void => {
                                console.error('Error getting AadHttpClient:', error);
                        });
        };

        return (
                <div>
                        <button onClick={fetchUserData}>Fetch User Data</button>
                        {hello ? <div>Hello, {hello.hello}</div> : <div>Loading...</div>}
                </div>
        );
};

export default HelloUser;

问题现象

  • SPFx本地工作台调用时返回401错误
  • SharePoint站点中WebPart调用时同样返回401错误
  • CORS已配置但未解决问题

技术排查与解决建议

1. 修正令牌受众配置

Azure Function身份验证设置中的允许的令牌受众应配置为应用注册的应用ID URI(即https://tenantname.onmicrosoft.com/ClientID_of_AzureAppRegistration_of_My_Function),而非应用注册的客户端ID。当前配置的受众不匹配SPFx请求的令牌受众,导致验证失败。

2. 对齐SPFx资源ID与应用ID URI

SPFx代码中getClient方法传入的资源ID必须与Azure Function应用注册的应用ID URI完全一致。检查当前代码中的https://artetechnology.onmicrosoft.com/8bdb826f-bbbc-4f6b-923a-63777a5c3550是否与实际配置的应用ID URI完全匹配(包括租户名称、客户端ID等细节)。

3. 调整客户端应用要求限制

当前设置的“仅允许来自自身应用的请求”会阻止SPFx的请求(SPFx使用SharePoint内置应用身份)。需将此选项修改为允许来自任何已注册应用的请求,或手动添加SPFx的应用ID(00000003-0000-0ff1-ce00-000000000000)到允许列表中。

4. 启用访问令牌并配置SPFx权限

  • 在Azure Function身份验证设置中,除了允许ID令牌,还需允许访问令牌(access token),因为AadHttpClient默认请求的是访问令牌。
  • 在SPFx的package-solution.json中添加对Azure Function的权限请求:
    "webApiPermissionRequests": [
      {
        "resource": "AuthenticationFunctionAppAZF",
        "scope": "user_impersonation"
      }
    ]
    
    部署解决方案后,需在SharePoint管理员中心批准该权限。

5. 完善CORS配置

  • 添加本地开发地址:https://localhost:4321
  • 添加SharePoint站点根地址(如https://yourtenant.sharepoint.com)
  • 确保“允许凭据”选项已勾选,因为SPFx请求会携带身份凭据。

6. 统一颁发者URL与租户设置

当前颁发者URL使用common端点,但租户要求限制为指定租户。将颁发者URL修改为指定租户的v2端点:https://login.microsoftonline.com/[你的租户ID]/v2.0,确保颁发者与租户限制一致。

7. 调试令牌内容

使用浏览器开发者工具捕获请求的Authorization令牌,通过JWT解析工具查看:

  • aud字段是否等于Azure Function的应用ID URI
  • iss字段是否匹配指定租户的颁发者URL
  • scp字段是否包含user_impersonation作用域

内容的提问来源于stack exchange,提问作者Jakub Husařík

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 09:22:02