如何在 Blazor WebAssembly MSAL 中处理多个资源的令牌

2024-01-08

这个问题没有真正的解决方案,只有各种解决方法,直到 net7 出现。在 net7 中,我们应该再次能够根据以下条件在一次调用中从多个源请求范围:https://github.com/dotnet/aspnetcore/pull/43954 https://github.com/dotnet/aspnetcore/pull/43954


我有一个 .net5 blazor web assembly 应用程序,使用 msal auth for azure 设置。

services.AddMsalAuthentication(options =>
{
    configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxxxx/API.Access"); // API
    options.ProviderOptions.Cache.CacheLocation = "localStorage";
}); 

这很好用。

此外,我需要访问 Microsoft graph。我已经使用 graph sdk 完成了此操作,并为 graph sdk 提供了身份验证处理程序

public class GraphAuthenticationProvider : IAuthenticationProvider
{
    private readonly NavigationManager _navigationManager;

    public GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, NavigationManager navigationManager)

    {
        TokenProvider = tokenProvider;
        _navigationManager = navigationManager;
    }

    public IAccessTokenProvider TokenProvider { get; }

    public async Task AuthenticateRequestAsync(HttpRequestMessage request)
    {
        string[] scopes = new[] { "https://graph.microsoft.com/Mail.ReadWrite", "https://graph.microsoft.com/Mail.Send" };

        var result = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions()
            {
                Scopes = scopes,
                ReturnUrl = _navigationManager.Uri
            });

        if (result.TryGetToken(out var token))
        {
            request.Headers.Authorization ??= new AuthenticationHeaderValue(
                "Bearer", token.Value);
        }
        else
        {
            _navigationManager.NavigateTo(result.RedirectUrl);
        }
    }
}

这似乎是根据我能找到的文档执行此操作的方法,尽管它似乎假设您正在尝试在同一资源上获取其他范围。https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassemble/additional-scenarios?view=aspnetcore-5.0#request-additional-access-tokens https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0#request-additional-access-tokens

这有两个问题:

  • 我找不到任何方法可以通过弹出窗口执行此操作,这意味着我被迫导航到重定向,从而导致丢失程序状态。这可以解决,但似乎同意的弹出版本应该是可能的?
  • 当身份验证完成并且我返回到我自己的应用程序时,它会尝试为所有范围(包括我的 api 和图表)创建一个令牌,这显然会因多个资源错误而失败,将我转到登录错误页面。即使它实际上正确地获取了两个令牌,我也可以简单地离开错误页面并访问我的 api 和图表。

我无法找到具有多个资源的 blazor web assembly msal 的任何文档。 有人可以解释我做错了什么或指出我正确的文档吗?

public class GraphAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public GraphAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(authorizedUrls: new[] { "https://graph.microsoft.com/" }, scopes: new[] { "https://graph.microsoft.com/Mail.ReadWrite", "https://graph.microsoft.com/Mail.Send" });
    }
}
services.AddScoped<CustomAuthorizationMessageHandler>();
services.AddScoped<GraphAuthenticationProvider>();

services.AddScoped<GraphHttpProvider>();
services.AddScoped<GraphAuthorizationMessageHandler>();

services.AddHttpClient<GraphHttpProvider>(
    client => client.BaseAddress = new Uri("https://graph.microsoft.com"))
    .AddHttpMessageHandler<GraphAuthorizationMessageHandler>();

services.AddScoped(sp =>
     new GraphServiceClient(
        sp.GetRequiredService<GraphAuthenticationProvider>(),
        sp.GetRequiredService<GraphHttpProvider>())
);

编辑:相关github问题https://github.com/dotnet/aspnetcore/issues/33241 https://github.com/dotnet/aspnetcore/issues/33241- 目前看来此功能存在缺陷。


我也很难找到高质量的例子。以下是我如何解决从 Web assembly(托管或独立)应用程序调用 1 个或多个 API 的问题。

大多数 MSFT 示例仅处理一种 Api,因此在通过 AddMsalAuthentication 注册 Msal 时使用 options.ProviderOptions.DefaultAccessTokenScopes 选项。这会将您的令牌锁定为单一受众,当您有多个 api 需要调用时,这将不起作用。

相反,从 AuthorizationMessageHandler 类派生每个 api 端点的处理程序,设置authorizedUrland范围,为 DI 容器中的每个端点注册命名的 HttpClient,并使用 IHttpClientFactory 生成 HttpClient。

设想: 假设我有一个 WebAssembly 应用程序(托管或独立),它调用多个受保护的 api,包括 microsoft graph api。

首先,我必须为从 AuthorizationRequestMessageHandler 派生的每个 api 创建一个类:

Api 1:

// This message handler handles calls to the api at the endpoint  "https://localhost:7040".  It will generate tokens with the right audience and scope
// "aud": "api://aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
// "scp": "access_as_user",
public class ApiOneAuthorizationRequestMessageHandler : AuthorizationMessageHandler
{
    // ILogger if you want..
    private readonly ILogger<ApiOneAuthorizationRequestMessageHandler> logger = default!;
    public ApiOneAuthorizationRequestMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager,
        ILoggerFactory loggerFactory
        )
        : base(provider, navigationManager)
    {
        logger = loggerFactory.CreateLogger<ApiOneAuthorizationRequestMessageHandler>() ?? throw new ArgumentNullException(nameof(logger));

        logger.LogDebug($"Setting up {nameof(ApiOneAuthorizationRequestMessageHandler)} to authorize the base url: {"https://localhost:7090/"}");
        ConfigureHandler(
           authorizedUrls: new[] { "https://localhost:7040" },
           scopes: new[] { "api://aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/access_as_user" });
    }
}

Api 2:

// This message handler handles calls to the api at the endpoint  "https://localhost:7090".  Check out the scope and audience through https://jwt.io
// "aud": "api://bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
// "scp": "access_as_user",
public class ApiTwoAuthorizationRequestMessageHandler : AuthorizationMessageHandler
{
    public ApiTwoAuthorizationRequestMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager
        )
        : base(provider, navigationManager)
    {
        ConfigureHandler(
           authorizedUrls: new[] { "https://localhost:7090" },
           scopes: new[] { "api://bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/access_as_user" });
    }
}

MS 图形 API:

// This message handler handles calls to Microsoft graph.
// "aud": "00000003-0000-0000-c000-000000000000"
// "scp": "Calendars.ReadWrite email MailboxSettings.Read openid profile User.Read",
public class GraphApiAuthorizationRequestMessageHandler : AuthorizationMessageHandler
{
    public GraphApiAuthorizationRequestMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager
        )
        : base(provider, navigationManager)
    {
        ConfigureHandler(
           authorizedUrls: new[] { "https://graph.microsoft.com" },
           scopes: new[] { "User.Read", "MailboxSettings.Read", "Calendars.ReadWrite" });
    }
}

现在,使用上面的端点 AuthorizationMessageHandler 为每个端点注册一个命名的 HttpClient。在 Program.cs 中执行此操作:

名为“ProductsApi”的 HttpClient

//register the AuthorizationRequestMessageHandler
builder.Services.AddScoped<ApiOneAuthorizationRequestMessageHandler>();
//register the named HttpClient 
builder.Services.AddHttpClient("ProductsApi",
    httpClient => httpClient.BaseAddress = new Uri("https://localhost:7040"))
    .AddHttpMessageHandler<ApiOneAuthorizationRequestMessageHandler>();

名为“MarketingApi”的 HttpClient:

builder.Services.AddScoped<ApiTwoAuthorizationRequestMessageHandler>();
builder.Services.AddHttpClient("MarketingApi",
    httpClient => httpClient.BaseAddress = new Uri("https://localhost:7090"))
    .AddHttpMessageHandler<ApiTwoAuthorizationRequestMessageHandler>();

名为“MSGraphApi”的 HttpClient

builder.Services.AddScoped<GraphApiAuthorizationRequestMessageHandler>();
builder.Services.AddHttpClient("MSGraphApi",
    httpClient => httpClient.BaseAddress = new Uri("https://graph.microsoft.com"))
    .AddHttpMessageHandler<GraphApiAuthorizationRequestMessageHandler>();

注册指定的 HttpClient 后,将 Msal 与 AzureAd 应用程序设置一起注册到 Program.cs 中。

无客户用户索赔的 Msal 注册:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

如果您通过 GraphApi 按照 Microsoft Doc 进行自定义用户帐户声明,则您的添加 Msal 应如下所示:

使用自定义用户声明进行 Msal 注册:

builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, GraphUserAccountFactory>();

要使用 GraphServiceClient,需要 GraphClientFactory。它将需要使用 IHttpClientFactory 创建正确命名的 HttpClient(例如 MSGraphApi)。

图客户端工厂:

public class GraphClientFactory
{
    private readonly IAccessTokenProviderAccessor accessor;
    private readonly IHttpClientFactory httpClientFactory;
    private readonly ILogger<GraphClientFactory> logger;
    private GraphServiceClient graphClient;

    public GraphClientFactory(IAccessTokenProviderAccessor accessor,
        IHttpClientFactory httpClientFactory,
        ILogger<GraphClientFactory> logger)
    {
        this.accessor = accessor;
        this.httpClientFactory = httpClientFactory;
        this.logger = logger;
    }

    public GraphServiceClient GetAuthenticatedClient()
    {
        HttpClient httpClient;

        if (graphClient == null)
        {
            httpClient = httpClientFactory.CreateClient("MSGraphApi");

            graphClient = new GraphServiceClient(httpClient)
            {
                AuthenticationProvider = new GraphAuthProvider(accessor)
            };
        }

        return graphClient;
    }
}

您还需要在 Program.cs 中注册 GraphClientFactory。

builder.Services.AddScoped<GraphClientFactory>();

要访问 Marketing Api,请注入 IHttpClientFactory 并创建一个命名的 HttpClient。

@inject IHttpClientFactory httpClientFactory

<h3>Example Component</h3>

@code {

    protected override async Task OnInitializedAsync()
    {
        try {
            var httpClient = httpClientFactory.CreateClient("MarketingApi");
            var resp = await httpClient.GetFromJsonAsync<APIResponse>("api/Function1");
            FunctionResponse = resp.Value;
            Console.WriteLine("Fetched " + FunctionResponse);
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

现在,通过访问 MarketingApi,您还可以通过使用此 MSFT 教程页面上描述的组件,使用 Graph Api 访问您的日历:

[第 4 步 - 显示日历事件][1]

访问 ProductsApi 与访问 MarketingApi 非常相似。

我希望这可以帮助人们在 Blazor Webassemble 中使用正确的访问令牌访问 Api。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Blazor WebAssembly MSAL 中处理多个资源的令牌 的相关文章

随机推荐