You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor WebAssembly独立应用访问已认证ASP.NET Core Web API问题

解决方案:Blazor WASM独立应用对接ASP.NET Core API认证

核心问题分析

你的场景中,Blazor WASM独立应用(跨端口)无法复用API的认证状态,本质是独立模式下Blazor没有内置的ASP.NET Core身份认证集成,需要手动处理令牌的存储、附加到后续请求头,或者配置跨域Cookie共享。以下是两种最直接的实现方案,优先推荐JWT令牌方式(跨域更简单)。


方案1:JWT令牌认证(推荐)

假设你的API /login 接口返回JWT格式的访问令牌,按以下步骤实现:

1. 客户端创建认证服务

负责登录、令牌存储与读取:

public class AuthService
{
    private readonly HttpClient _httpClient;
    private readonly IJSRuntime _jsRuntime;

    public AuthService(HttpClient httpClient, IJSRuntime jsRuntime)
    {
        _httpClient = httpClient;
        _jsRuntime = jsRuntime;
    }

    // 登录请求,存储令牌到LocalStorage
    public async Task<bool> Login(string email, string password)
    {
        var loginPayload = new { Email = email, Password = password };
        var response = await _httpClient.PostAsJsonAsync("/login", loginPayload);
        
        if (!response.IsSuccessStatusCode)
            return false;

        var tokenResult = await response.Content.ReadFromJsonAsync<LoginResponse>();
        await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "authToken", tokenResult.AccessToken);
        return true;
    }

    // 退出登录,清除令牌
    public async Task Logout()
    {
        await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "authToken");
    }

    // 获取存储的令牌
    public async Task<string?> GetToken()
    {
        return await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "authToken");
    }
}

// 对应API返回的令牌结构
public class LoginResponse
{
    public string AccessToken { get; set; } = string.Empty;
}

2. 自定义请求消息处理器

自动将令牌附加到所有API请求的Authorization头:

public class AuthMessageHandler : DelegatingHandler
{
    private readonly IJSRuntime _jsRuntime;

    public AuthMessageHandler(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "authToken");
        
        if (!string.IsNullOrEmpty(token))
        {
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

3. 配置客户端Program.cs

注册服务并配置带认证的HttpClient:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// 注册认证服务
builder.Services.AddScoped<AuthService>();
// 注册自定义消息处理器
builder.Services.AddScoped<AuthMessageHandler>();

// 配置API客户端,自动附加令牌
builder.Services.AddHttpClient("ApiClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5001/"); // 替换为你的API地址
})
.AddHttpMessageHandler<AuthMessageHandler>();

// 注册便捷使用的HttpClient实例
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient"));

await builder.Build().RunAsync();

4. 登录与数据组件示例

登录组件(Login.razor)

@page "/login"
@inject AuthService AuthService
@inject NavigationManager NavManager

<EditForm Model="@loginModel" OnValidSubmit="@HandleLogin">
    <DataAnnotationsValidator />
    <div class="mb-3">
        <label class="form-label">邮箱</label>
        <InputText @bind-Value="loginModel.Email" class="form-control" />
        <ValidationMessage For="@(() => loginModel.Email)" />
    </div>
    <div class="mb-3">
        <label class="form-label">密码</label>
        <InputText type="password" @bind-Value="loginModel.Password" class="form-control" />
        <ValidationMessage For="@(() => loginModel.Password)" />
    </div>
    <button type="submit" class="btn btn-primary">登录</button>
    @if (loginFailed)
    {
        <p class="text-danger mt-2">凭证错误,请重试</p>
    }
</EditForm>

@code {
    private LoginModel loginModel = new();
    private bool loginFailed;

    private async Task HandleLogin()
    {
        loginFailed = !await AuthService.Login(loginModel.Email, loginModel.Password);
        if (!loginFailed)
        {
            NavManager.NavigateTo("/data");
        }
    }

    public class LoginModel
    {
        [Required]
        [EmailAddress]
        public string Email { get; set; } = string.Empty;

        [Required]
        public string Password { get; set; } = string.Empty;
    }
}

数据组件(Data.razor)

@page "/data"
@inject HttpClient HttpClient
@inject AuthService AuthService
@inject NavigationManager NavManager

<h3>API返回数据</h3>

@if (data == null)
{
    <p>加载中...</p>
}
else
{
    <pre>@System.Text.Json.JsonSerializer.Serialize(data, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })</pre>
}

<button @onclick="@HandleLogout" class="btn btn-secondary mt-3">退出登录</button>

@code {
    private object? data;

    protected override async Task OnInitializedAsync()
    {
        var token = await AuthService.GetToken();
        if (string.IsNullOrEmpty(token))
        {
            NavManager.NavigateTo("/login");
            return;
        }

        try
        {
            data = await HttpClient.GetFromJsonAsync<object>("/data");
        }
        catch (HttpRequestException ex)
        {
            if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
            {
                NavManager.NavigateTo("/login");
            }
        }
    }

    private async Task HandleLogout()
    {
        await AuthService.Logout();
        NavManager.NavigateTo("/login");
    }
}

方案2:Cookie跨域认证(如果API用Cookie认证)

如果你的API使用Cookie而非JWT,需要配置跨域Cookie共享:

1. API端CORS配置

builder.Services.AddCors(options =>
{
    options.AddPolicy("BlazorCors", policy =>
    {
        policy.WithOrigins("https://localhost:5002") // 替换为Blazor应用地址
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials(); // 允许携带Cookie
    });
});

// 中间件中启用CORS(放在UseRouting之后,UseAuthorization之前)
app.UseCors("BlazorCors");

2. API端Cookie配置

修改Cookie的SameSite属性以支持跨域:

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.SameSite = SameSiteMode.None;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 仅HTTPS环境生效
});

3. 客户端HttpClient配置

让HttpClient自动携带Cookie:

builder.Services.AddScoped(sp =>
{
    var handler = new HttpClientHandler
    {
        UseCookies = true,
        CookieContainer = new System.Net.CookieContainer()
    };
    return new HttpClient(handler)
    {
        BaseAddress = new Uri("https://localhost:5001/") // API地址
    };
});

关键注意事项

  1. API端CORS必须正确配置:确保允许Blazor应用的来源地址,JWT模式不需要AllowCredentials,Cookie模式必须开启。
  2. HTTPS要求:跨域Cookie共享必须在HTTPS环境下测试(Localhost默认支持),否则SameSite=None的Cookie会被浏览器拦截。
  3. 令牌过期处理:可在AuthMessageHandler中添加令牌过期检测逻辑,自动跳转到登录页。

内容的提问来源于stack exchange,提问作者Robin Wilson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:44:52