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

ASP.NET Core应用同时保留授权码流与客户端凭据流的解决方案

同时在ASP.NET Core中保留Authorization Code Flow与Client Credentials Flow

问题背景

我有一个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:05:54