在 Blazor wasm 中自动将访问令牌附加到 HTTP 客户端

And*_*w F 5 c# asp.net-core blazor blazor-client-side blazor-webassembly

我正在为我的 Blazor wasm 应用程序使用开放 id 连接身份提供程序,并且希望将访问令牌附加到 http 客户端,如本文所述。

但是,每当我创建 http 客户端并尝试使用它时,即使已登录,我也会收到 AccessTokenNotAvailableException 异常。

这是我所拥有的:

在程序.cs中

// Add service for CustomAuthorizationMessageHandler
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

// Http client for requests to API
builder.Services.AddHttpClient("API", client =>
{
    // Set base address to API url
    client.BaseAddress = new Uri("https://localhost:44370");

    // Set request headers to application/json
    client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

// Auth0 authentication setup
builder.Services.AddOidcAuthentication(options =>
{
    builder.Configuration.Bind("Auth0", options.ProviderOptions);
    options.ProviderOptions.ResponseType = "code";
});
Run Code Online (Sandbox Code Playgroud)

自定义AuthorizationHandler.cs

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://localhost:44370" },
            scopes: new[] { "admin" });
    }
}
Run Code Online (Sandbox Code Playgroud)

当尝试访问 api 时

try
{
    var client = ClientFactory.CreateClient("API");
    message = await client.GetStringAsync("api/message");
}
catch (AccessTokenNotAvailableException e)
{
    message = "You CANNOT access the api. Please log in";
}
Run Code Online (Sandbox Code Playgroud)

目前,以下代码可以在删除 Program.cs 中的 AddHttpMessageHandler 调用时从 api 获取消息,但我不想每次进行 api 调用时都必须获取访问令牌。TokenProvider 是 IAccessTokenProvider 类型并被注入。

var client = ClientFactory.CreateClient("API");
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "api/message"))
{
    var tokenResult = await TokenProvider.RequestAccessToken();

    if (tokenResult.TryGetToken(out var token))
    {
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
        var response = await client.SendAsync(requestMessage);
        if(response.IsSuccessStatusCode)
        {
            message = await response.Content.ReadFromJsonAsync<string>();
        }
        else
        {
            message = await response.Content.ReadAsStringAsync();
        }
    }
    else
    {
        message = "You CANNOT access the api. Please log in";
    }
}
Run Code Online (Sandbox Code Playgroud)

如何解决此问题,以免每次都出现 AccessTokenNotAvailableException?

Cob*_*byC 11

已经明确指出,有两种方法可以为传出请求配置消息处理程序,建议实现自定义消息处理程序。再次......您实现一个自定义消息处理程序以配置消息处理程序。这就是定制的唯一目的。当然,您可以重写 SendAsync 方法,如果这样的操作到位,您就知道自己在做什么,并且可以适当地执行该操作。但并不是引入错误并实际上使 SendAsync 无效的一种方式。

这是自定义消息处理程序的实现:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
Run Code Online (Sandbox Code Playgroud)

--

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
    {
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
            NavigationManager navigationManager)
            : base(provider, navigationManager)
        {
            ConfigureHandler(
               authorizedUrls: new[] { "https://localhost:44370" });
    
        }
    }   
Run Code Online (Sandbox Code Playgroud)

上面解决了您遇到的问题...也就是说,配置范围属性(scopes: new[] { "admin" });),可以省略。

以下是改进代码的一些建议: 创建一个命名的 HttpClient 服务,如下所示。

builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory> 
   ().CreateClient("ServerAPI"));
Run Code Online (Sandbox Code Playgroud)

并将其注入到您的组件中,如下所示:@inject HttpClient Http

并像这样使用它:

protected override async Task OnInitializedAsync()
    {
        try
        {
           message= await Http.GetFromJsonAsync<string>("api/message");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    } 
Run Code Online (Sandbox Code Playgroud)

注意:为了使用 GetFromJsonAsync 方法,您应该安装 System.Net.Http.Json 包

注意,上面是处理Http请求的正确方法。这包括将用户重定向到登录页面。当然,您可以生成想要向用户显示的任何消息,但重定向是正确的方法。

应用上述建议的更改后,您的 Program.Main 方法应具有以下设置(注意顺序):

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

            builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
               .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
                
            builder.Services.AddTransient(sp => 
   sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
Run Code Online (Sandbox Code Playgroud)