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

Duende Server无法生成含令牌的Cookie问题求助

问题描述

我在https://localhost:5001部署了IdentityServer,使用React自定义登录页(测试阶段采用简易HTML文件)。点击登录按钮跳转至携带ReturnUrl的登录页后,通过axios发送的请求能正常到达Login控制器,但返回Ok(context.RedirectUri)后,浏览器仅生成aspnet cookie,未包含access_token、refresh_token、id_token及用户信息。


Login控制器代码

[HttpPost("login")]
public async Task<IActionResult> Login(string username, string password, string returnUrl)
{
    // check if we are in the context of an authorization request
    var context = await _interaction.GetAuthorizationContextAsync(returnUrl);

    if (context == null)
    {
        return Redirect("~/");
    }

    var user = await _userManager.FindByNameAsync(username);

    if(await _userManager.CheckPasswordAsync(user, password))
    {
        var claims = new List<Claim>
        {
            new("Sub", user.Id),
            new(ClaimTypes.Name, user.Email),
            new("FullName", user.Name),
            new(ClaimTypes.Role, "User")
        };

        var claimsIdentity = new ClaimsIdentity(
            claims, CookieAuthenticationDefaults.AuthenticationScheme);

        var authProperties = new AuthenticationProperties
        {
            AllowRefresh = true,
            ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(20),
            IsPersistent = true,
            IssuedUtc = DateTimeOffset.UtcNow,
        };

        await HttpContext.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            new ClaimsPrincipal(claimsIdentity),
            authProperties);

        return Ok(context.RedirectUri);
    }

    return BadRequest("Something went wrong");
}

HostingExtensions.cs代码

public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
{
    var migrationsAssembly = typeof(Program).Assembly.GetName().Name;
    var connectionString = builder.Configuration.GetConnectionString("EFConn");

    builder.Services.AddControllers();

    builder.Services.AddDbContext<DbContext>(opt =>
    {
        opt.UseNpgsql(connectionString, opt => opt.MigrationsAssembly(migrationsAssembly));
    });

    builder.Services.AddIdentity<User, IdentityRole>()
        .AddEntityFrameworkStores<DbContext>()
        .AddDefaultTokenProviders();

    builder.Services.AddIdentityServer(options =>
    {
        // I had to override the IReturnUrlParser class
        options.UserInteraction.LoginUrl = "https://localhost:5003/login.html";
        options.UserInteraction.ErrorUrl = "http://localhost:5001/error.html";
        options.UserInteraction.LogoutUrl = "http://localhost:8082/logout.html";

        options.EmitStaticAudienceClaim = true;

        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseSuccessEvents = true;
    })
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
            sql => sql.MigrationsAssembly(migrationsAssembly));
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
            sql => sql.MigrationsAssembly(migrationsAssembly));
    })
    .AddAspNetIdentity<User>();

    builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
        options.SlidingExpiration = true;
        options.AccessDeniedPath = "/Forbidden/";
    });

    var cors = new DefaultCorsPolicyService(new  LoggerFactory().CreateLogger<DefaultCorsPolicyService>()){ AllowAll = true };

    builder.Services.AddSingleton<ICorsPolicyService>(cors);
    builder.Services.AddTransient<IReturnUrlParser, ReturnUrlParser>();

    builder.Services.AddCors(options =>
    {
        // this defines a CORS policy called "default"
        options.AddPolicy("default", policy =>
        {
            policy
              .WithMethods("GET", "POST", "PATCH", "DELETE", "OPTIONS")
              .AllowAnyHeader()
              .WithOrigins("https://localhost:5003");
        });
    });

    return builder.Build();
}

public static WebApplication ConfigurePipeline(this WebApplication app)
{
    app.UseSerilogRequestLogging();

    if (app.Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    InitializeDatabase(app);

    app.UseCors("default");

    app.UseStaticFiles();
    app.UseRouting();

    app.UseIdentityServer();

    app.MapControllers();

    return app;
}

解决方案

问题核心:仅完成了用户本地Cookie登录,但未让IdentityServer继续执行授权流程生成令牌;跨域场景下Cookie未正确携带,导致IdentityServer无法识别已认证用户。

修正步骤

  1. 前端axios请求开启Cookie携带
    在登录请求中配置withCredentials: true,确保跨域请求时携带IdentityServer的认证Cookie:

    axios.post('https://localhost:5001/login', 
      { username: 'xxx', password: 'xxx', returnUrl: 'xxx' },
      { withCredentials: true }
    )
    
  2. 修改登录控制器的返回逻辑
    替换return Ok(context.RedirectUri)为直接重定向,让浏览器跳转至IdentityServer授权端点,触发令牌颁发流程:

    return Redirect(context.RedirectUri);
    
  3. 调整Cookie的跨域配置
    在AddCookie中设置Cookie的SameSite和Secure属性,适配跨域场景:

    builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
        options.SlidingExpiration = true;
        options.AccessDeniedPath = "/Forbidden/";
        options.Cookie.SameSite = SameSiteMode.None;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.HttpOnly = true;
    });
    
  4. 验证ReturnUrl解析正确性
    确保自定义的IReturnUrlParser能正确解析ReturnUrl,保证context.RedirectUri是合法的IdentityServer授权回调地址。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:37:50