如何配置Razor Pages项目通过Cookie认证调用WebApi的Identity端点
我有一个包含所有业务逻辑端点的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,同时配置跨域相关参数:
- 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");
- 调整两个项目的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"; });
- 前端用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
相关产品推荐
相关产品推荐

