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

Blazor Server对接LDAP实现Cookie认证后浏览器未生成Cookie问题求助

问题根因

你遇到的Cookie不生成的核心原因是Blazor Server的代码运行逻辑导致的:
Blazor Server的组件C#代码是完全运行在服务端的,你在Login.razor.cs中注入HttpClient发起登录接口请求,本质是服务器自己向自己发起了HTTP请求,接口返回的Set-Cookie头只会保存在服务端HttpClient的Cookie容器中,根本不会发送到用户的浏览器,所以浏览器永远不会生成认证Cookie。

除此之外还有几个次要错误:

  • 登录请求的URL拼接错误:$"NavMan.BaseUri}authentication/login" 少了左大括号,正确写法应为$"{NavMan.BaseUri}authentication/login"(如果继续用接口方式的话需要修正)
  • Startup.cs中没有注册IHttpContextAccessor,也没有完善Cookie认证的配置项
  • 绕远路调用自身服务的登录接口,完全没有必要

修复方案

方案1:直接在Blazor登录逻辑中处理认证(推荐)

不需要额外写认证Controller,直接在登录逻辑中完成LDAP校验和Cookie签发:

  1. 修正Startup.cs配置
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddControllersWithViews().AddRazorRuntimeCompilation();
    // 注册IHttpContextAccessor用于获取当前用户的HttpContext
    services.AddHttpContextAccessor();
    // 完善Cookie认证配置
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            options.LoginPath = "/login";
            options.Cookie.Name = ".YourAppName.AuthCookie";
            options.Cookie.HttpOnly = true;
            options.Cookie.SameSite = SameSiteMode.Lax;
            options.ExpireTimeSpan = TimeSpan.FromDays(1);
        });
}
// Configure方法的中间件顺序不用改,你现在的顺序是对的
  1. 重写Login.razor.cs的登录逻辑
public partial class Login
{
    public string Username { get; set; }
    public string Password { get; set; }
    [Parameter]
    public string ErrorMessage { get; set; }
    [Inject]
    private IHttpContextAccessor HttpContextAccessor { get; set; }
    [Inject]
    private NavigationManager NavMan { get; set; }

    private async Task PerformLoginAsync()
    {
        if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
        {
            ErrorMessage = "请输入用户名和密码";
            return;
        }

        string path = "LDAP://serveraddress.xxx";
        try
        {
            using DirectoryEntry entry = new(path, Username, Password);
            using DirectorySearcher searcher = new(entry);
            searcher.Filter = $"(&(objectclass=user)(objectcategory=person)(samaccountname={Username}))";
            var result = searcher.FindOne();
            if (result != null)
            {
                List<Claim> claims = new();
                claims.Add(new Claim(ClaimTypes.Name, Username));
                // 处理角色
                foreach (var group in result.Properties["memberof"])
                {
                    var distinguishedName = new X500DistinguishedName(group.ToString());
                    var commonNameData = new AsnEncodedData("CN", distinguishedName.RawData);
                    var commonName = commonNameData.Format(false);
                    if (!string.IsNullOrEmpty(commonName))
                    {
                        claims.Add(new Claim(ClaimTypes.Role, commonName));
                    }
                }
                // 处理邮箱
                foreach (var email in result.Properties["mail"])
                {
                    claims.Add(new Claim(ClaimTypes.Email, email.ToString()));
                }

                var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                var authProperties = new AuthenticationProperties
                {
                    AllowRefresh = true,
                    ExpiresUtc = DateTimeOffset.Now.AddDays(1),
                    IsPersistent = true,
                };
                // 直接给当前用户的HttpContext签发Cookie
                await HttpContextAccessor.HttpContext.SignInAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme, 
                    new ClaimsPrincipal(claimsIdentity), 
                    authProperties);
                // 强制刷新页面加载新的Cookie
                NavMan.NavigateTo("/", true);
            }
            else
            {
                ErrorMessage = "用户不存在";
            }
        }
        catch (Exception)
        {
            ErrorMessage = "用户名或密码错误";
        }
    }
}
  1. 可以删除不需要的AuthenticationController相关代码。

方案2:保留认证Controller的实现

如果你希望保留Controller的登录接口,需要让请求从用户浏览器发起,而不是服务端发起:

  1. 把登录逻辑改成HTML表单提交,或者用JS互操作调用fetch发起POST请求,示例表单写法:
<!-- 修改Login.razor,用原生表单提交 -->
<form action="/authentication/login" method="post">
    <MudTextField @bind-Value="@Username" T="string" Label="Username" Name="Username"/>
    <MudTextField @bind-Value="@Password" T="string" Label="Password" Name="Password" InputType="InputType.Password"/>
    <button type="submit">Sign In</button>
</form>
  1. 对应修改Controller的Login方法,接收表单参数,校验通过后直接重定向到根目录即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 21:06:03