ASP.NET Core应用同时保留授权码流与客户端凭据流的解决方案
问题背景
我有一个ASP.NET Core应用,当前基于Authorization Code Flow实现用户登录,通过委派权限完成用户列表查询、日历查看等操作。现在需要新增呼叫功能,该功能要求使用Client Credentials Flow获取应用级权限,但尝试添加Client Credentials相关配置时,出现了认证方案重复的错误(提示Scheme已存在)。
原认证配置代码如下:
var builder = WebApplication.CreateBuilder(args); // Configure authentication builder.Services // Use OpenId authentication .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) // Specify this is a web app and needs auth code flow .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAd", options); // This causes the signin to prompt the user for which // account to use - useful when there are multiple accounts signed // into the browser options.Prompt = "select_account"; options.Events.OnTokenValidated = async context => { var tokenAcquisition = context.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new BaseBearerTokenAuthenticationProvider( new TokenAcquisitionTokenProvider( tokenAcquisition, GraphConstants.Scopes, context.Principal))); // Get user information from Graph var user = await graphClient.Me .GetAsync(config => { config.QueryParameters.Select = ["displayName", "mail", "mailboxSettings", "userPrincipalName"]; }); context.Principal?.AddUserGraphInfo(user); }; options.Events.OnAuthenticationFailed = context => { var error = WebUtility.UrlEncode(context.Exception.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}"); context.HandleResponse(); return Task.FromResult(0); }; options.Events.OnRemoteFailure = context => { if (context.Failure is OpenIdConnectProtocolException) { var error = WebUtility.UrlEncode(context.Failure.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}"); context.HandleResponse(); } return Task.FromResult(0); }; }) // Add ability to call web API (Graph) // and get access tokens .EnableTokenAcquisitionToCallDownstreamApi( options => { builder.Configuration.Bind("AzureAd", options); }, GraphConstants.Scopes) // Add a GraphServiceClient via dependency injection .AddMicrosoftGraph(options => { options.Scopes = GraphConstants.Scopes; }) // Use in-memory token cache .AddInMemoryTokenCaches(); // Require authentication builder.Services .AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }) // Add the Microsoft Identity UI pages for sign in/out .AddMicrosoftIdentityUI(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
尝试添加的Client Credentials配置代码:
// Add the Microsoft Identity Web App services for Client Credentials Flow builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "Azure") .EnableTokenAcquisitionToCallDownstreamApi( options => { builder.Configuration.Bind("Azure", options); }, GraphConstants.ScopesApp) .AddInMemoryTokenCaches();
错误表现:提示认证方案已存在,无法重复注册。
解决方案
可以同时保留两种认证流,核心是为Client Credentials Flow配置独立的认证方案名称,避免与默认的Authorization Code Flow方案冲突,同时单独注册应用上下文的TokenAcquisition与GraphServiceClient。
完整配置代码
var builder = WebApplication.CreateBuilder(args); // 1. 保留原有Authorization Code Flow配置(用户登录+委派权限调用) builder.Services .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAd", options); options.Prompt = "select_account"; options.Events.OnTokenValidated = async context => { var tokenAcquisition = context.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new BaseBearerTokenAuthenticationProvider( new TokenAcquisitionTokenProvider( tokenAcquisition, GraphConstants.Scopes, context.Principal))); var user = await graphClient.Me .GetAsync(config => { config.QueryParameters.Select = ["displayName", "mail", "mailboxSettings", "userPrincipalName"]; }); context.Principal?.AddUserGraphInfo(user); }; options.Events.OnAuthenticationFailed = context => { var error = WebUtility.UrlEncode(context.Exception.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}"); context.HandleResponse(); return Task.CompletedTask; }; options.Events.OnRemoteFailure = context => { if (context.Failure is OpenIdConnectProtocolException) { var error = WebUtility.UrlEncode(context.Failure.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}"); context.HandleResponse(); } return Task.CompletedTask; }; }) .EnableTokenAcquisitionToCallDownstreamApi(GraphConstants.Scopes) .AddMicrosoftGraph(options => { options.Scopes = GraphConstants.Scopes; }) .AddInMemoryTokenCaches(); // 2. 添加Client Credentials Flow独立配置(应用权限调用) builder.Services .AddAuthentication("ClientCredentialsScheme") .AddMicrosoftIdentityWebApi(options => { builder.Configuration.Bind("AzureAd", options); options.TokenValidationParameters.ValidateAudience = true; options.TokenValidationParameters.ValidAudience = options.ClientId; }, options => { builder.Configuration.Bind("AzureAd", options); }) .EnableTokenAcquisitionToCallDownstreamApi(GraphConstants.ScopesApp) .AddInMemoryTokenCaches(); // 注册应用上下文的GraphServiceClient builder.Services.AddScoped<GraphServiceClient>(sp => { var tokenAcquisition = sp.GetRequiredService<ITokenAcquisition>(); return new GraphServiceClient( new BaseBearerTokenAuthenticationProvider( new TokenAcquisitionTokenProvider( tokenAcquisition, GraphConstants.ScopesApp))); }); // 全局授权策略(保留用户登录要求) builder.Services .AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }) .AddMicrosoftIdentityUI(); var app = builder.Build(); // 请求管道配置不变 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
关键要点
- 方案隔离:为Client Credentials指定专属的认证方案名称
"ClientCredentialsScheme",避免与默认的OpenIdConnect/Cookie方案冲突。 - 双Graph实例:通过依赖注入注册两个
GraphServiceClient,分别对应用户上下文(委派权限)和应用上下文(应用权限),调用时自动匹配对应的令牌获取逻辑。 - 配置兼容:确保
appsettings.json中的AzureAd节点包含ClientId、ClientSecret、TenantId等Client Credentials所需信息。
应用权限调用示例
在需要使用应用权限的控制器中,注入GraphServiceClient即可直接调用:
public class CallController : Controller { private readonly GraphServiceClient _appGraphClient; public CallController(GraphServiceClient appGraphClient) { _appGraphClient = appGraphClient; } public async Task<IActionResult> InitiateCall() { // 使用应用权限调用Microsoft Graph的呼叫接口 var callRequest = new Call { // 呼叫请求参数 }; var result = await _appGraphClient.Communications.Calls.PostAsync(callRequest); return View(result); } }
内容的提问来源于stack exchange,提问作者Adalid Bori Palma
相关产品推荐
相关产品推荐

