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

Blazor .NET8 Server Interactive持久化认证实现问题求助

Blazor .NET8 Server Interactive 持久化认证实现方案

问题描述

我是一名WinForm/WPF开发者,刚接触Web开发,目前在Blazor .NET8 Server Interactive应用中无法实现可持久化的认证系统。登录登出功能可正常工作,网站内导航时用户状态也能保留,但刷新页面(F5)或打开新标签页后,用户就会失去登录状态。我明白这是因为用户状态存储在AuthenticationManager的私有字段中,现有的自定义AuthenticationStateProvider代码如下:

public class AuthenticationManager(ILogger<AuthenticationManager> logger) : AuthenticationStateProvider
{
  private ClaimsPrincipal _user = new();

  public override Task<AuthenticationState> GetAuthenticationStateAsync()
  {
    return Task.FromResult(new AuthenticationState(_user));
  }

  public ClaimsPrincipal? LogInUser(string decryptedName, string decryptedPassword)
  {
    try
    {
      // 检查是否提供了用户名和密码
      if (!string.IsNullOrEmpty(decryptedName) && !string.IsNullOrEmpty(decryptedPassword))
      {
        // 在数据库中查找对应用户
        using DBInterface dbManager = new();
        TUser? dbUser = dbManager.GetAllUsers().FirstOrDefault(u => string.Compare(decryptedName, u.Name, true) == 0);

        // 验证密码
        if (dbUser != null && Simple3Des.DecryptString(dbUser.PasswordHash) == decryptedPassword)
        {
          List<Claim> claims =
          [
            new(ClaimTypes.Name, decryptedName),
            new(ClaimTypes.Role, dbUser.AccessLevelEnu.ToString())
          ];
          ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);

          _user = new(claimsIdentity);

          NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
          return _user;
        }
      }
    }
    catch (Exception ex)
    {
      logger!.LogError("Erreur dans {methodName} : {exceptionType} : {exceptionMessage}", $"{nameof(AuthenticationManager)}.{nameof(LogInUser)}", ex.GetType().Name, ex.Message);
      throw;
    }
    return null;
}

public void LogOutUser()
  {
    _user = new();
    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
  }
}

解决方案

要实现持久化认证,核心是把用户的认证状态存储到Cookie中(Web应用的标准持久化方式),而不是仅存在服务端内存里。以下是具体实现步骤:

1. 配置Cookie认证服务

在Program.cs中添加Cookie认证的配置:

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Login"; // 未认证时跳转的登录页路径
        options.Cookie.Name = "BlazorAuthCookie"; // Cookie名称
        options.Cookie.HttpOnly = true; // 禁止JS读取Cookie,提升安全性
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境建议启用,仅通过HTTPS传输
        options.ExpireTimeSpan = TimeSpan.FromDays(7); // Cookie有效期
    });

// 必须添加授权服务
builder.Services.AddAuthorization();

同时要确保在中间件管道中启用认证和授权:

app.UseAuthentication();
app.UseAuthorization();

2. 修改自定义AuthenticationManager

注入IHttpContextAccessor来操作Cookie,同时修改GetAuthenticationStateAsync从Cookie读取认证状态,登录时写入Cookie,登出时删除Cookie:

public class AuthenticationManager(ILogger<AuthenticationManager> logger, IHttpContextAccessor httpContextAccessor) : AuthenticationStateProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null)
        {
            return new AuthenticationState(new ClaimsPrincipal());
        }

        // 从Cookie中获取认证票据
        var authResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        return new AuthenticationState(authResult.Principal ?? new ClaimsPrincipal());
    }

    public async Task<ClaimsPrincipal?> LogInUser(string decryptedName, string decryptedPassword)
    {
        try
        {
            if (!string.IsNullOrEmpty(decryptedName) && !string.IsNullOrEmpty(decryptedPassword))
            {
                using DBInterface dbManager = new();
                TUser? dbUser = dbManager.GetAllUsers().FirstOrDefault(u => string.Compare(decryptedName, u.Name, true) == 0);

                if (dbUser != null && Simple3Des.DecryptString(dbUser.PasswordHash) == decryptedPassword)
                {
                    List<Claim> claims =
                    [
                        new(ClaimTypes.Name, decryptedName),
                        new(ClaimTypes.Role, dbUser.AccessLevelEnu.ToString())
                    ];
                    ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

                    // 创建认证票据并写入Cookie
                    await _httpContextAccessor.HttpContext!.SignInAsync(
                        CookieAuthenticationDefaults.AuthenticationScheme,
                        claimsPrincipal,
                        new AuthenticationProperties
                        {
                            IsPersistent = true, // 持久化Cookie,关闭浏览器后仍保留
                            ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7)
                        });

                    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
                    return claimsPrincipal;
                }
            }
        }
        catch (Exception ex)
        {
            logger!.LogError("Erreur dans {methodName} : {exceptionType} : {exceptionMessage}", $"{nameof(AuthenticationManager)}.{nameof(LogInUser)}", ex.GetType().Name, ex.Message);
            throw;
        }
        return null;
    }

    public async Task LogOutUser()
    {
        await _httpContextAccessor.HttpContext!.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }
}

然后在Program.cs中注册IHttpContextAccessor和自定义AuthenticationStateProvider:

builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<AuthenticationStateProvider, AuthenticationManager>();

3. 关键注意事项

  • 密码存储风险:当前代码中对数据库存储的密码哈希进行解密后验证,这是非常不安全的做法。正确的方式是永远不存储明文密码,也不解密存储的哈希,应该在用户注册时对明文密码进行哈希(比如使用BCrypt或ASP.NET Core Identity的密码哈希器),验证时直接将输入的明文密码哈希后与数据库中的哈希值比对。
  • Blazor Server交互模式:Server Interactive模式下,IHttpContextAccessor在组件初始化时可正常获取上下文,但后台线程中可能无法访问,不过登录登出操作通常由用户交互触发,不会有问题。
  • Cookie安全性:生产环境务必启用SecurePolicy = CookieSecurePolicy.Always,确保Cookie仅通过HTTPS传输,防止被窃听。

内容的提问来源于stack exchange,提问作者jérémie Courbat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:34:57