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

如何配置Razor Pages项目通过Cookie认证调用WebApi的Identity端点

跨项目共享Cookie登录问题

我有一个包含所有业务逻辑端点的WebApi项目,已实现登录/注册等Identity端点,API的Cookie认证功能正常,登录后可调用授权端点。同时有一个Razor页面项目,计划让Razor页面完全通过调用API完成所有操作,不在Razor页面的.cs文件中实现业务逻辑。

已完成的配置:

  • 两个项目都配置了相同的DataProtection密钥(已复制密钥文件,生产环境将部署在不同子域名/托管空间)
  • 两个项目都配置了同名的共享Cookie .MarketManager.SharedCookie
  • Person类继承自IdentityUser
  • 直接通过Swagger访问API功能正常,Razor页面的_loginpartial.cshtml能识别Cookie并显示用户名

问题:通过Razor页面调用登录端点时,接口返回成功,但浏览器未生成Cookie,页面直接跳转回登录页。


Razor项目Program.cs代码

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddRazorPages();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(builder.Environment.ContentRootPath + "/keys"))
    .SetApplicationName("MarketManager");

builder.Services
    .AddDbContext<ApplicationDbContext>(
        options => options
            .UseSqlServer(connectionString));

builder.Services
    .AddDefaultIdentity<Person>()
    .AddRoles<AppRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = ".MarketManager.SharedCookie";
    options.Cookie.Path = "/";
    options.LoginPath = "/Login";
    options.LogoutPath = "/Logout";
});

var app = builder.Build();

app.Use(async (context, next) =>
{
    await next();
    if (context.Response.StatusCode == 404)
    {
        context.Request.Path = "/not-found";
        await next();
    }
});

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

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

app.MapRazorPages();

app.Run();

API项目Program.cs代码

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDomainServices(builder.Configuration);
builder.Services.AddApplicationServices();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(builder.Environment.ContentRootPath + "/keys"))
    .SetApplicationName("MarketManager");

builder.Services
    .AddDefaultIdentity<Person>()
    .AddRoles<AppRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = ".MarketManager.SharedCookie";
    options.Cookie.Path = "/";
    options.LoginPath = "/Login";
    options.LogoutPath = "/Logout";
});

builder.Services.AddAuthorization();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapIdentityApi<Person>();

app.MapGroup("/roles")
    .MapRoles();

await app.RunAsync();

Razor登录页OnPostAsync代码

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    if (ModelState.IsValid)
    {
        using (var client = new HttpClient())
        {
            var response = await client.PostAsJsonAsync("http://localhost:5122/identity/login?useCookies=true", Input);
            switch (response.StatusCode)
            {
                case System.Net.HttpStatusCode.OK:
                    return Redirect(returnUrl);
                case System.Net.HttpStatusCode.BadRequest:
                    var errorText = response.Content.ReadAsStringAsync().Result;
                    ModelState.AddModelError(string.Empty, errorText);
                    return Page();
            }
        }
    }

    // Incorrect input
    return Page();
}

问题原因及解决方案

核心原因

你用HttpClient在服务器端调用API,API返回的Cookie会被HttpClient接收,而不是传递给浏览器。浏览器完全不知道这次服务器端的HTTP请求和响应,自然不会写入Cookie。这是典型的服务器端代理请求丢失客户端Cookie上下文的问题。

解决方案

方案1:让浏览器直接提交表单到API

修改登录页的表单,直接指向API的登录端点,跳过服务器端的HttpClient调用:

<form method="post" action="http://localhost:5122/identity/login?useCookies=true">
    <input type="text" name="UserName" placeholder="用户名" />
    <input type="password" name="Password" placeholder="密码" />
    <input type="hidden" name="returnUrl" value="@Model.ReturnUrl" />
    <button type="submit">登录</button>
</form>

方案2:配置CORS与跨域Cookie属性

如果需要保留Razor页面的前端交互逻辑,可通过AJAX调用API,同时配置跨域相关参数:

  1. API项目添加CORS配置
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowRazorApp", policy =>
    {
        policy.WithOrigins("http://localhost:xxxx") // 替换为你的Razor项目端口
              .AllowCredentials() // 允许携带Cookie
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

// 在请求管道中启用CORS,位置在UseRouting之后、UseAuthorization之前
app.UseCors("AllowRazorApp");
  1. 调整两个项目的Cookie跨域配置
    在ConfigureApplicationCookie中补充跨域相关属性:
builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = ".MarketManager.SharedCookie";
    options.Cookie.Path = "/";
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.None; // 开发环境用None,生产环境改为Always
    options.Cookie.SameSite = SameSiteMode.None; // 跨域场景必须设为None
    options.LoginPath = "/Login";
    options.LogoutPath = "/Logout";
});
  1. 前端用AJAX调用登录接口
document.getElementById('loginForm').addEventListener('submit', async function(e) {
    e.preventDefault();
    const formData = new FormData(this);
    
    const response = await fetch('http://localhost:5122/identity/login?useCookies=true', {
        method: 'POST',
        credentials: 'include', // 关键:携带并接收Cookie
        body: formData
    });

    if (response.ok) {
        window.location.href = '@Model.ReturnUrl';
    } else {
        const error = await response.text();
        // 页面上显示错误信息
        document.getElementById('errorMsg').textContent = error;
    }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:44:52