.NET 8 Blazor Web 应用程序,具有使用 JWT 身份验证的 Web API

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

我有两个 .NET 8 项目,一个 ASP.NET Core 8 Web API 和 Blazor Web 应用程序,交互式渲染模式设置为服务器。

Web API 处理身份验证并提供 JWT 令牌。获取令牌的注册和登录工作正常。

当我尝试使用

<AuthorizeView>
时出现问题。我将其添加到
Home.razor
组件作为基本测试,但随后遇到了错误:

此时无法发出 JavaScript 互操作调用

我一直无法解决这个问题。

Program.cs

using AuthDemo.Blazor.Server.UI.Components;
using AuthDemo.Blazor.Server.UI.Infrastructure.Services.HttpClients;
using Blazored.LocalStorage;
using AuthDemo.Blazor.Server.UI.Infrastructure.Services.Authentication;
using Microsoft.AspNetCore.Components.Authorization;
using AuthDemo.Blazor.Server.UI.Infrastructure.Providers.Authentication;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddCascadingAuthenticationState();

builder.Services.AddBlazoredLocalStorage();

builder.Services.AddHttpClient<IAuthDemoApiClient, AuthDemoApiClient>(cl => cl.BaseAddress = new Uri("https://localhost:7287"));
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(p => p.GetRequiredService<CustomAuthenticationStateProvider>());

builder.Services.AddRouting(options =>
{
    options.LowercaseUrls = true;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

App.razor

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="AuthDemo.Blazor.Server.UI.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet @rendermode="InteractiveServer" />
</head>

<body>
    <Routes @rendermode="InteractiveServer" />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

AuthenticationService.cs

using AuthDemo.Blazor.Server.UI.Infrastructure.Services.HttpClients;
using Microsoft.AspNetCore.Identity.Data;

namespace AuthDemo.Blazor.Server.UI.Infrastructure.Services.Authentication
{
    public class AuthenticationService : IAuthenticationService
    {
        private readonly IAuthDemoApiClient _AuthDemoApiClient;

        public AuthenticationService(IAuthDemoApiClient AuthDemoApiClient)
        {
            _AuthDemoApiClient = AuthDemoApiClient;
        }

        public async Task<AuthenticationResponseDto?> LoginAsync(LoginDto loginDto)
        {
            var result = await _AuthDemoApiClient.LoginAsync(loginDto);
            return result;
        }
    }
}

CustomAuthenticationStateProvider.cs

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace AuthDemo.Blazor.Server.UI.Infrastructure.Providers.Authentication
{
    public class CustomAuthenticationStateProvider : AuthenticationStateProvider
    {

        private readonly ILocalStorageService _localStorageService;
        private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
        private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;

        public CustomAuthenticationStateProvider(ILocalStorageService localStorageService)
        {
            _localStorageService = localStorageService;
            _jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var token = await _localStorageService.GetItemAsync<string>("accessToken");

            if (string.IsNullOrEmpty(token))
            {
                return new AuthenticationState(_anonymous);
            }

            var tokenContent = _jwtSecurityTokenHandler.ReadJwtToken(token);
            var claims = tokenContent.Claims;
            var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));

            return await Task.FromResult(new AuthenticationState(user));
        }

        public void AuthenticateUser(string token)
        {
            var tokenContent = _jwtSecurityTokenHandler.ReadJwtToken(token);
            var claims = tokenContent.Claims;
            var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
            var state = new AuthenticationState(user);
            NotifyAuthenticationStateChanged(Task.FromResult(state));
        }
    }
}

Login.razor

@page "/auth/login"

@inject IAuthenticationService _authenticationService
@inject AuthenticationStateProvider _authenticationStateProvider
@inject IAuthDemoApiClient _httpAuthDemoApiClient;
@inject ILocalStorageService _localStorageService;
@inject NavigationManager _navigationManager;

<h3>Login</h3>

@if (!string.IsNullOrEmpty(exMessage))
{
    <div class="alert alert-danger">
        <p>@exMessage</p>
    </div>
}

<div class="card-body">
    <EditForm Model="LoginDto" OnValidSubmit="HandleLogin">
        <DataAnnotationsValidator />
        <ValidationSummary />

        <div class="form-group">
            <label for="EmailAddress">Email Address</label>
            <InputText class="form-control" @bind-Value="LoginDto.EmailAddress" />
            <ValidationMessage For="@(() => LoginDto.EmailAddress)" />
        </div>
        <br />

        <div class="form-group">
            <label for="Password">Password</label>
            <InputText type="password" class="form-control" @bind-Value="LoginDto.Password" />
            <ValidationMessage For="@(() => LoginDto.Password)" />
        </div>

        <button type="submit" class="btn btn-primary btn-block">Login</button>
    </EditForm>
</div>

@code {
    LoginDto LoginDto = new LoginDto();
    string exMessage = string.Empty;

    private async Task HandleLogin()
    {
        try
        {
            var authResponse = await _authenticationService.LoginAsync(LoginDto);
            if (authResponse != null)
            {
                await _localStorageService.SetItemAsync("token", authResponse.Token);
                ((CustomAuthenticationStateProvider)_authenticationStateProvider).AuthenticateUser(authResponse.Token!);
                _navigationManager.NavigateTo("/");
            }
        }
        catch (ApiException ex)
        {
            exMessage = ex.Response;
        }
        catch (Exception ex)
        {
            exMessage = ex.Message;
        }
    }
}

以上工作正常,我可以登录并取回令牌。

当我添加以下内容时出现问题:

Home.razor

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<AuthorizeView>
    <Authorized>
        <h1>logged in</h1>
    </Authorized>
    <NotAuthorized>
        <h1>not logged in</h1>
    </NotAuthorized>
</AuthorizeView>

当我运行该项目时,出现以下错误:

如果我将

<AuthorizeView>
添加到
Routes.razor
,我也会遇到同样的问题。

我正在尝试了解正确的方法是什么......

asp.net-core jwt blazor blazor-server-side .net-8.0
1个回答
0
投票

<Routes @rendermode="InteractiveServer" />
实际上是“ServerSignalR”+“SSR预渲染”模式。但在预渲染中,c#代码是在服务器中执行的,它无法访问浏览器本地存储。
_localStorageService.GetItemAsync
仅适用于“ServerSignalR”模式。
因此,使这些代码正常工作的最简单方法是在 App.razor 中禁用预渲染,如下所示:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(false)" />
...
<Routes @rendermode="new InteractiveServerRenderMode(false)" />

由于您使用的是InteractiveServer,禁用SSR不会对用户体验产生太大影响。但是当您使用 WASM 时,禁用 SSR 将使用户必须等待 blazor 资源下载完毕。在这种情况下,不能选择将令牌保存在本地存储中。

© www.soinside.com 2019 - 2024. All rights reserved.