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

4
我正在为我的 Blazor wasm 应用程序使用一个 open id connect 身份提供程序,希望像这篇文章所述的那样将访问令牌附加到 http 客户端。
然而,每当我创建 http 客户端并尝试使用它时,即使已登录,我也会收到 AccessTokenNotAvailableException 异常。
这是我的代码:
在 Program.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";
});

CustomAuthorizationHandler.cs

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

尝试访问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";
}

目前,当我在 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";
    }
}

我该如何修复这个问题,以免每次都出现AccessTokenNotAvailableException异常?

你使用哪个身份提供者?是IdentityServer4还是其他的东西? - CobyC
@CobyC 我们正在使用 Auth0。 - Andrew F
好的,你在Microsoft网站上添加了一个链接,但是还有一篇Auth0文章介绍了Auth0的实现。我只做过IdentityServer4的实现。我在你的Program.cs设置中看不到像Auth0文章中提到的AddOidcAuthentication() - CobyC
@CobyC 我编辑了我的帖子,展示了我在 Program.cs 中的 Auth0 oicd 实现。 - Andrew F
@CobyC 我使用了那篇 Auth0 文章来设置身份验证。 - Andrew F
1个回答

9
已经明确说明了有两种配置出站请求消息处理程序的方法,建议实现自定义消息处理程序。 再次强调......您需要实现自定义消息处理程序以配置消息处理程序。这是个性化的唯一目的。当然,如果有这样的操作,您可以覆盖SendAsync方法,您知道自己在做什么,并适当地执行。但不要将其作为引入错误和使SendAsync无效的方式。
以下是自定义消息处理程序的实现:
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[] { "https://localhost:44370" });
    
        }
    }   

上面解决了您遇到的问题......也就是说,配置作用域属性(scopes: new[] { "admin" });)可以省略。
以下是改进代码的一些建议: 创建一个命名的HttpClient服务,如下所示。
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory> 
   ().CreateClient("ServerAPI"));

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

使用方法如下:

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

注意:为了使用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"));

事实证明,ConfigureHandler方法中的scopes参数给我带来了麻烦,所以我将其删除后就可以正常工作了。我没有想到要重写SendAsync方法,但我认为那也可以起作用。感谢您的帮助! - Andrew F
很高兴你把它搞定了!授权和认证是相当复杂的主题,有时候文档并不能很好地解释清楚。 - CobyC
我会注释掉范围行以备后用。 - CobyC
1
@CobyC,除非您是接受答案的用户,否则您无法删除已接受的答案。然而,既然我的回答未被接受,我通常会将我的答案移动到您的帖子中并删除我的帖子。 - enet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接