获取数据返回对受Azure AD保护的asp.net核心API的非结构化401访问

获取数据返回对受Azure AD保护的asp.net核心API的非结构化401访问,api,azure-active-directory,bearer-token,blazor-webassembly,Api,Azure Active Directory,Bearer Token,Blazor Webassembly,我是新来webassembly blazor的,我花了太多时间想弄清楚这里出了什么问题,但我无法管理 我有以下情况: Asp.net API在Azure AD中注册并受保护 公开作用域为AcessApi且状态为已启用的API 将客户端应用程序添加到授权客户端应用程序中 令牌配置检查访问令牌和ID令牌 以及一个调用API的客户端应用程序,在webassembly blazor 客户端应用程序已在Azure AD中注册 客户端API权限已授予使用我的客户端API的权限 具有正确的作用域AccessA

我是新来webassembly blazor的,我花了太多时间想弄清楚这里出了什么问题,但我无法管理

我有以下情况:

  • Asp.net API在Azure AD中注册并受保护
  • 公开作用域为
    AcessApi
    且状态为
    已启用的API
  • 将客户端应用程序添加到
    授权客户端应用程序中
  • 令牌配置检查访问令牌和ID令牌
  • 以及一个调用API的客户端应用程序,在
    webassembly blazor

  • 客户端应用程序已在Azure AD中注册
  • 客户端API权限已授予使用我的客户端API的权限 具有正确的作用域
    AccessApi
  • 我使用swagger接口测试了API,它强制用户在访问API之前首先进行身份验证。 我使用
    curl
    进行了测试,从swagger界面抓取了令牌,效果非常好

    curl -X GET "http://localhost:9400/api/getdata" -H  "accept: text/plain" -H  "Authorization: Bearer XX" 
    
    但是,当我的客户端应用程序尝试访问API时,会弹出一个凭据登录页面,我可以看到正在检索浏览器栏上的令牌ID,调用API时,应用程序会记录错误
    未授权

    客户端应用程序的程序类:

            public static async Task Main(string[] args)
            {
                var builder = WebAssemblyHostBuilder.CreateDefault(args);
                builder.RootComponents.Add<App>("app");
                //builder.Logging.SetMinimumLevel(LogLevel.Debug);
    
    
                ////builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
                builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
    
                builder.Services.AddHttpClient("AccessApi",
                   client => client.BaseAddress = new Uri("http://localhost:9400"))
                  .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
    
                builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
                    .CreateClient("AccessApi"));
    
               builder.Services.AddMsalAuthentication(options =>
                {
                   builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
                    options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
                });
    
                await builder.Build().RunAsync();
            }
    
    appsettings.json
    a中定义了API的客户端id和租户id,但没有作用域,因为它们是在
    CustomAuthorizationMessageHandler
    类中定义的:

    {
      "AzureAd": {
        "Authority": "https://login.microsoftonline.com/<tenant_id>",
        "ClientId": "<clientid>",
        "CallbackPath": "/signin-oidc",
        "ValidateAuthority": "true"
      }
    }
    
    
    控制台日志

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
          Authorization was successful.
    info: System.Net.Http.HttpClient.AccessApi.ClientHandler[100]
          Sending HTTP request GET http://localhost:9400/api/getdata
    :9400/customer-manager/api/getdata:1 Failed to load resource: the server responded with a status of 401 (Unauthorized)
    
    这里可能出了什么问题? 有没有办法打印返回令牌

    更新


    我使用Postman测试了API,其中auth-Grant类型是
    隐式的
    ,成功登录后,我将令牌存储在变量上,并作为
    承载者
    传入头部,API返回
    401
    未授权。我解码了令牌,它包含正确的作用域
    AccessApi
    ,具有正确的clientId。这里可能有什么问题?

    如果您想在一个blazor webassembly项目中调用Microsoft graph和自定义API,我们可以通过创建不同的HTTP客户端来调用不同的API来实现

    比如说

    • 注册服务器API应用程序

    • 为服务器API应用程序注册AAD应用程序
    • 注册客户端应用程序

    • 注册客户端应用程序
    • 添加API权限。(API应用程序权限)
    • 配置API应用程序

    请在
    Startup.cs

     public void ConfigureServices(IServiceCollection services)
     {
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
            services.AddCors(options =>
                {
                    options.AddDefaultPolicy(
                        builder => builder.AllowAnyOrigin()
                            .AllowAnyHeader()
                            .AllowAnyMethod());
                });
                   services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
                    .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
    
                services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
                {
                    options.Authority += "/v2.0";
    
    
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        
                        ValidIssuers = new[] {
                          $"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
                          $"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
    
                        },
                        RoleClaimType = "roles",
                        // The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
                        ValidAudiences = new[]
                        {
                               options.Audience,
                               $"api://{options.Audience}"
                        }
    
                    };
    
                });
    ....
    }
      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.OAuthClientId(Configuration["Swagger:ClientId"]);
                    c.OAuthScopeSeparator(" ");
                    c.OAuthAppName("Protected Api");
    
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                });
                app.UseHttpsRedirection();
    
                app.UseRouting();
                app.UseCors();
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
    
    public void配置服务(IServiceCollection服务)
    {
    JwtSecurityTokenHandler.DefaultMapInboundClaims=false;
    services.AddCors(选项=>
    {
    options.AddDefaultPolicy(
    builder=>builder.AllowAnyOrigin()
    .AllowAnyHeader()
    .AllowAnyMethod());
    });
    services.AddAuthentication(AzureADDefaults.BeareAuthenticationScheme)
    .AddAzureADBearer(options=>Configuration.Bind(“AzureAd”,options));
    Configure(AzureADDefaults.JwtBearerAuthenticationScheme,选项=>
    {
    选项。权限+=“/v2.0”;
    options.TokenValidationParameters=新的TokenValidationParameters
    {
    ValidIssuers=new[]{
    $"https://sts.windows.net/{配置[“AzureAD:TenantId”]}/“,
    $"https://login.microsoftonline.com/{配置[“AzureAD:TenantId”]}/v2.0
    },
    RoleClaimType=“角色”,
    //web API接受客户机ID(options.acquisition)和API://{ClientID}作为访问群体。
    有效期=新[]
    {
    选择,观众,
    $“api://{options.viewer}”
    }
    };
    });
    ....
    }
    public void配置(IApplicationBuilder应用程序、IWebHostEnvironment环境)
    {
    if(env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }
    app.UseSwagger();
    app.UseSwaggerUI(c=>
    {
    c、 OAuthClientId(配置[“Swagger:ClientId]”);
    c、 OAuthScope分隔符(“”);
    c、 OAuthAppName(“受保护的Api”);
    c、 SwaggerEndpoint(“/swagger/v1/swagger.json”,“我的API v1”);
    });
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseCors();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(端点=>
    {
    endpoints.MapControllers();
    });
    }
    
    • 配置客户端应用程序
  • 为图形API和自定义API创建自定义AuthorizationMessageHandler
  • //自定义API
    使用Microsoft.AspNetCore.Components;
    使用Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    公共类CustomAuthorizationMessageHandler:AuthorizationMessageHandler
    {
    公共CustomAuthorizationMessageHandler(IAccessTokenProvider提供程序,
    导航管理器(导航管理器)
    :base(提供程序、navigationManager)
    {
    配置处理程序(
    authorizedUrls:new[]{”“},
    作用域:新[]{“API应用程序作用域”});
    }
    }
    
  • 将以下代码添加到program.cs
  • 公共类程序
    {
    公共静态异步任务主(字符串[]args)
    {
    var builder=WebAssemblyHostBuilder.CreateDefault(args);
    
    builder.RootComponents.Add

    最后,我发现问题出在服务器端ASP.net core上,我在
    启动时在
    配置服务
    中验证令牌:

     // For token parameters validation
                services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(o =>
                {
                    o.Audience = "<xx>"; // Application id 
                    o.Authority = "https://login.microsoftonline.com/<xx>"; // Tenant ID 
                    //Token validation
                    o.TokenValidationParameters = new TokenValidationParameters {ValidateIssuerSigningKey = false, ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true};
    
                });
    
    //用于令牌参数验证
    服务
    
     public void ConfigureServices(IServiceCollection services)
     {
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
            services.AddCors(options =>
                {
                    options.AddDefaultPolicy(
                        builder => builder.AllowAnyOrigin()
                            .AllowAnyHeader()
                            .AllowAnyMethod());
                });
                   services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
                    .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
    
                services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
                {
                    options.Authority += "/v2.0";
    
    
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        
                        ValidIssuers = new[] {
                          $"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
                          $"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
    
                        },
                        RoleClaimType = "roles",
                        // The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
                        ValidAudiences = new[]
                        {
                               options.Audience,
                               $"api://{options.Audience}"
                        }
    
                    };
    
                });
    ....
    }
      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.OAuthClientId(Configuration["Swagger:ClientId"]);
                    c.OAuthScopeSeparator(" ");
                    c.OAuthAppName("Protected Api");
    
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                });
                app.UseHttpsRedirection();
    
                app.UseRouting();
                app.UseCors();
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
    
    // custom API
    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
    {
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
            NavigationManager navigationManager)
            : base(provider, navigationManager)
        {
            ConfigureHandler(
                authorizedUrls: new[] { "<your web API url>" },
                scopes: new[] { "the API app scope" });
        }
    }
    
    public class Program
        {
            public static async Task Main(string[] args)
            {
                var builder = WebAssemblyHostBuilder.CreateDefault(args);
                builder.RootComponents.Add<App>("app");
    
                builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
               
                // register HTTP client to call our own api
                builder.Services.AddHttpClient("MyAPI", client => client.BaseAddress = new Uri("<your web API url>"))
                  .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
               
      
                builder.Services.AddMsalAuthentication(options =>
                {
                    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
                    options.ProviderOptions.DefaultAccessTokenScopes.Add("<the API app scope>");
                    
                });
    
                await builder.Build().RunAsync();
            }
        }
    
    @inject IHttpClientFactory _clientFactory
    
    var httpClient = _clientFactory.CreateClient("<the client name you register>");
    await apiClient.GetStringAsync("path");
    
     // For token parameters validation
                services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(o =>
                {
                    o.Audience = "<xx>"; // Application id 
                    o.Authority = "https://login.microsoftonline.com/<xx>"; // Tenant ID 
                    //Token validation
                    o.TokenValidationParameters = new TokenValidationParameters {ValidateIssuerSigningKey = false, ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true};
    
                });