Azure Function V4依赖注入调用异常排查(PnPCore证书认证)
Azure Function V4 + PnPCore 证书认证空引用问题排查与临时解决
更新记录
更新 2023-02-03 2
已找到临时解决方法并修复问题,具体方案见下文。
更新 2023-02-03 1
已定位异常发生在PnPCoreAuthenticationCredentialConfigurationOptions的证书配置环节:
X509Certificate = new PnPCoreAuthenticationX509CertificateOptions { Certificate = certificate }
推测依赖注入初始化Function1时,证书数据未被正确持久化。
问题背景
使用.NET6开发Azure Function V4,通过PnPCore实现SharePoint Online认证,采用Azure Key Vault存储的X509Certificate进行身份验证。应用注册、证书及Key Vault权限均已验证无误(通过PowerShell可正常访问SPO),但函数执行时抛出空引用异常:
System.Private.CoreLib: Exception has been thrown by the target of an invocation. Anonymously Hosted DynamicMethods Assembly: Object reference not set to an instance of an object.
移除builder.Services.AddPnPCoreAuthentication代码后,函数可正常启动,但执行_pnpContextFactory.CreateAsync时会因缺少认证失败。
配置文件(local.settings.json)
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "", "FUNCTIONS_WORKER_RUNTIME": "dotnet" }, "SharePointApp": { "ClientId": "6f5020cb-xxxx-xxxx-xxxx-xxxxxxxxxxx", "ClientSecret": "A6q****l6", "TenantId": "7bcac91e-xxxx-xxxx-xxxx-xxxxxxxxxxx", "ListServerRelativeUrl": "/sites/TestSite/SomeLibrary", "AzureKeyVault": { "Uri": "https://contoso-keys.vault.azure.net/", "AppCertificateKey": "My-integration-app-DEV" } }, "PnPCore": { "HttpRequests": { "UserAgent": "PnPCoreSDK" }, "PnPContext": { "GraphFirst": "false", "GraphCanUseBeta": "false" }, "Sites": { "SiteToWorkWith": { "SiteUrl": "https://contoso.sharepoint.com/sites/TestSite/" } } } }
Startup类代码
using Azure.Identity; using Azure.Security.KeyVault.Certificates; using Contoso.Function; using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using PnP.Core.Auth.Services.Builder.Configuration; using PnP.Core.Services.Builder.Configuration; using System; [assembly: FunctionsStartup(typeof(Startup))] namespace Contoso.Function { public class Startup : FunctionsStartup { private static IConfiguration _configuration = null; public override void Configure(IFunctionsHostBuilder builder) { var provider = builder.Services.BuildServiceProvider(); _configuration = provider.GetRequiredService<IConfiguration>(); var spOptions = new SharePointAppConfiguration(); // 绑定local.settings.json中SharePointApp节点的自定义类 _configuration.GetSection("SharePointApp").Bind(spOptions); var coreOptions = new PnPCoreOptions(); _configuration.GetSection("PnPCore").Bind(coreOptions); builder.Services.AddSingleton(spOptions); builder.Services.AddPnPCore(options => { options.PnPContext = coreOptions.PnPContext; options.HttpRequests = coreOptions.HttpRequests; foreach (var site in coreOptions.Sites) { options.Sites.Add(site.Key, site.Value); } }); builder.Services.AddPnPCoreAuthentication(options => { var azKeyVault = new CertificateClient(spOptions.AzureKeyVault.Uri, new ClientSecretCredential(spOptions.TenantId, spOptions.ClientId, spOptions.ClientSecret)); var certificate = azKeyVault.DownloadCertificate(spOptions.AzureKeyVault.AppCertificateKey); options.Credentials.Configurations.Add("x509certificate", new PnPCoreAuthenticationCredentialConfigurationOptions { ClientId = spOptions.ClientId, TenantId = spOptions.TenantId, X509Certificate = new PnPCoreAuthenticationX509CertificateOptions { Certificate = certificate } }); options.Credentials.DefaultConfiguration = "x509certificate"; options.Sites.Add("SiteToWorkWith", new PnPCoreAuthenticationSiteOptions { AuthenticationProviderName = "x509certificate" }); }); } public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder) { var context = builder.GetContext(); builder.ConfigurationBuilder .SetBasePath(Environment.CurrentDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); } } }
Function类代码
using System; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using PnP.Core.Services; namespace Contoso.Function { public class Function1 { private readonly IPnPContextFactory _pnpContextFactory; public Function1(IPnPContextFactory pnpContextFactory) { _pnpContextFactory = pnpContextFactory; } [FunctionName("Function1")] public void Run([TimerTrigger("*/5 * * * * *")] TimerInfo myTimer, ILogger log) { using (var context = _pnpContextFactory.CreateAsync("SitesToWorkWith")) { log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); } } } }
临时解决方法
避免在AddPnPCoreAuthentication中直接传递Key Vault下载的证书实例,改为通过证书的字节数组+密码方式配置:
- 从Key Vault获取证书的完整PFX字节数据(可通过Key Vault的Secret节点获取,或下载证书后导出为PFX格式)
- 修改认证配置代码:
// 从Key Vault获取证书PFX字节数据及密码(无密码则为空字符串) var certificateBytes = ...; var certificatePassword = ""; options.Credentials.Configurations.Add("x509certificate", new PnPCoreAuthenticationCredentialConfigurationOptions { ClientId = spOptions.ClientId, TenantId = spOptions.TenantId, X509Certificate = new PnPCoreAuthenticationX509CertificateOptions { CertificateBytes = certificateBytes, CertificatePassword = certificatePassword } });
这种方式确保证书数据在依赖注入容器中被正确序列化和持久化,避免直接传递证书实例导致的空引用问题。
内容的提问来源于stack exchange,提问作者diceler
相关产品推荐
相关产品推荐

