使用个人帐户使用 RequireAuthorization() 实现 Blazor 中的最少 API

问题描述 投票:0回答:1

在我的.NET 8 Blazor项目中,我添加了“个人帐户”来管理用户。该应用程序有 2 个项目。

服务器

服务器端包含登录部分和控制器/端点。

enter image description here

控制器/端点是像这样的最小 API

public static void MapClientEndpoints (this IEndpointRouteBuilder routes)
{
    var group = routes.MapGroup("/api/Client").WithTags(nameof(Client));

    group.MapGet("/", async (HypnoContext db) =>
    {
        return await db.Client.ToListAsync();
    })
    .RequireAuthorization()
    .WithName("GetAllClients")
    .WithOpenApi();
}

以便保存在数据库中。另外,我添加了

HttpClient
的配置:

builder.Services.AddScoped<AuthenticationStateProvider, 
    PersistingRevalidatingAuthenticationStateProvider>();

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

builder.Services.AddIdentityCore<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddSignInManager()
        .AddDefaultTokenProviders();

builder.Services
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"))
        .AddHttpClient("ServerAPI", (provider, client) =>
        {
            client.BaseAddress = new Uri(builder. Configuration["FrontendUrl"]);
        });

客户

客户项目拥有所有观点。视图调用服务器提供的 API。

enter image description here

Program.cs 中,此配置是开箱即用的:

builder.Services.AddSingleton<AuthenticationStateProvider, 
    PersistentAuthenticationStateProvider>();

为了从页面连接到 API,我创建了一个类来将所有调用分组到一个位置。构造函数是这样的

string baseEndpoint;
IHttpClientFactory ClientFactory;
HttpClient? httpClient;

public ApiService(string baseUrl, IHttpClientFactory clientFactory)
{
    baseEndpoint = baseUrl;
    ClientFactory = clientFactory;
    httpClient = ClientFactory.CreateClient("ServerAPI");
}

虽然我以用户身份登录应用程序,但只有在最小 API 中不需要授权的情况下,对 API 的调用才有效。就像

IHttpClientFactory
没有

如何保护 API 并从客户端使用它们?

我尝试了什么

在服务器的

Program.cs
中,我为
HttpClient
添加了以下代码:

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("ServerAPI", client => {
        client.BaseAddress = new Uri(builder.Configuration["FrontendUrl"]);
    })
   .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

builder.Services.AddTransient(sp =>
    sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));

CustomAuthorizationMessageHandler
如下

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

我这样做是因为我想调用受个人帐户保护的服务器项目中公开的 API。所以,在一个页面中

@inject HttpClient httpClient

HttpRequestMessage request = new HttpRequestMessage(
    HttpMethod.Get, "/api/Client");
await httpClient.SendAsync(request);

当我运行应用程序时,我立即收到错误:

System.AggregateException:“某些服务无法 构造(验证服务描述符时出错 '服务类型:MyApp.CustomAuthorizationMessageHandler 生命周期:范围实施类型: MyApp.CustomAuthorizationMessageHandler':无法解析 服务类型 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider' 尝试激活时 'MyApp.CustomAuthorizationMessageHandler'。)'

enter image description here

c# blazor blazor-webassembly .net-8.0 webapi
1个回答
0
投票

请按照我的步骤检查您的项目:

Hypno平台项目

1.controllers 文件夹包含静态类,如下所示

public static class ClientEndpint
{
    public static void MapClientEndpoints(this IEndpointRouteBuilder routes)
    {
        var group = routes.MapGroup("/api/Client");

        group.MapGet("/", async (ApplicationDbContext db) =>
        {
            return await db.Users.ToListAsync();
        })
        .RequireAuthorization()
        .WithName("GetAllClients").WithOpenApi();
    }
}

2.程序.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();


builder.Services
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"))
        .AddHttpClient("ServerAPI", (provider, client) =>
        {
            client.BaseAddress = new Uri("https://localhost:7216");
        });
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(BlazorApp1.Client._Imports).Assembly);

app.MapAdditionalIdentityEndpoints();
ClientEndpint.MapClientEndpoints(app);   //be sure register the api
app.Run();

HypnoPlatform.Client

1.在razor组件中调用HttpClient(为了方便测试,我使用了

Counter.razor

@page "/counter"
@inject HttpClient httpClient
    
<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        HttpRequestMessage request = new HttpRequestMessage(
    HttpMethod.Get, "/api/Client");
        await httpClient.SendAsync(request);
    }
}

2.Program.cs(还需要在

HypnoPlatform.Client
项目中注册Httpclient)

注意:不要像

builder. Configuration["FrontendUrl"]
项目那样使用
HypnoPlatform
,因为
HypnoPlatform.Client
项目中没有appsettings.json。

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();
builder.Services
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"))
        .AddHttpClient("ServerAPI", (provider, client) =>
        {
            client.BaseAddress = new Uri("https://localhost:7216");
        });
await builder.Build().RunAsync();
© www.soinside.com 2019 - 2024. All rights reserved.