.NET 8 Identity Endpoints Cookie跨应用授权问题求助
场景概述
使用.NET Identity平台及Identity API端点构建身份微服务,包含三个测试应用:
- Identity API:验证用户凭证并生成授权Cookie
- 客户端Razor Pages应用:向Identity API发送登录请求,需获取Cookie用于后续认证
- 测试API:模拟微服务,需通过Identity API的Cookie完成认证
现有代码
Identity API 的 Program.cs 代码
using IdentityService.Data; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using System.Security.Claims; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddAuthorization(); builder.Services.AddAuthentication().AddCookie(IdentityConstants.ApplicationScheme) .AddBearerToken(IdentityConstants.BearerScheme); builder.Services.AddIdentityCore<User>() .AddEntityFrameworkStores<AppDBContext>() .AddApiEndpoints(); builder.Services.AddDbContext<AppDBContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDatabase")); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapIdentityApi<User>(); app.Run();
客户端Razor Pages 登录Post方法代码
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } LoginRequest request = new LoginRequest { Email = Request.Form["Email"], Password = Request.Form["Password"] }; HttpClient client = _httpClientFactory.CreateClient("Auth"); HttpResponseMessage response = await client.PostAsJsonAsync(new Uri(client.BaseAddress + "login?useCookies=true"), request); string content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { return RedirectToPage("./Test"); } return RedirectToPage("./Index"); }
测试API 的 Program.cs 代码
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme); builder.Services.AddAuthorizationBuilder() .AddPolicy("api", p => { p.RequireAuthenticatedUser(); p.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme); }); builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys")).SetApplicationName("IdentityService.API"); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapGet("api/foo", () => { return new[] { "One", "Two", "Three" }; }) .RequireAuthorization("api"); app.Run();
测试API 受保护端点代码
namespace IdentityService.TestAPI.Controllers { [Route("api/[controller]")] [ApiController] [Authorize("api")] public class TestController : ControllerBase { private readonly ILogger<TestController> _logger; public TestController(ILogger<TestController> logger) { _logger = logger; } [HttpGet] public string Get() { return "Test API returned Successfully"; } } }
问题描述
客户端应用提交登录凭证并收到Identity API的成功响应后,浏览器未获取到预期的Cookie;但通过Swagger直接调用Identity API登录时可正常获取Cookie。需要实现:
- 客户端应用的浏览器能获取到Identity API生成的Cookie
- 后续浏览器访问测试API时,能自动携带该Cookie完成认证
解决方案
核心问题分析
当前客户端应用是后端代码通过HttpClient调用Identity API,登录成功后Cookie仅存储在后端的HttpClient容器中,并未转发给浏览器,因此浏览器无法获取。同时跨应用Cookie共享需要统一的加密密钥、Cookie配置及CORS设置。
步骤1:修改Identity API配置
1.1 配置Cookie跨域属性
修改AddCookie的配置,确保Cookie支持跨域:
builder.Services.AddAuthentication() .AddCookie(IdentityConstants.ApplicationScheme, options => { options.Cookie.Name = ".AspNetCore.Identity.Application"; options.Cookie.Domain = "localhost"; // 本地开发统一域名,生产环境改为实际域名 options.Cookie.SameSite = SameSiteMode.None; // 跨域场景必须设置为None options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS环境下强制Secure options.Cookie.HttpOnly = true; }) .AddBearerToken(IdentityConstants.BearerScheme);
1.2 配置共享数据保护密钥
确保和其他应用使用相同的密钥容器和应用名称:
builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys")) .SetApplicationName("IdentityService"); // 三个应用必须使用相同名称
1.3 添加CORS允许客户端凭证
builder.Services.AddCors(options => { options.AddPolicy("AllowClient", policy => { policy.WithOrigins("https://localhost:xxxx") // 替换为客户端应用的实际HTTPS地址 .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // 必须允许凭证(Cookie) }); });
1.4 启用CORS中间件
在UseHttpsRedirection之后添加:
app.UseCors("AllowClient");
步骤2:修改客户端Razor Pages配置
2.1 转发Identity API的Cookie到浏览器
在登录成功后,将Identity API返回的Set-Cookie头转发给客户端浏览器:
if (response.IsSuccessStatusCode) { // 提取Identity API返回的Cookie并添加到响应中 foreach (var cookieHeader in response.Headers.GetValues("Set-Cookie")) { Response.Headers.Append("Set-Cookie", cookieHeader); } return RedirectToPage("./Test"); }
2.2 配置HttpClient允许凭证(可选)
如果需要客户端后端后续调用测试API时携带Cookie,可在Program.cs中配置HttpClient:
builder.Services.AddHttpClient("Auth", client => { client.BaseAddress = new Uri("https://localhost:yyyy"); // 替换为Identity API的地址 }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseCookies = true, AllowCredentials = true, CookieContainer = new CookieContainer() });
步骤3:修改测试API配置
3.1 统一Cookie认证配置
修改AddAuthentication的配置,和Identity API保持一致:
builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme) .AddCookie(IdentityConstants.ApplicationScheme, options => { options.Cookie.Name = ".AspNetCore.Identity.Application"; options.Cookie.Domain = "localhost"; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.HttpOnly = true; });
3.2 统一数据保护配置
修改应用名称和Identity API一致:
builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys")) .SetApplicationName("IdentityService"); // 和Identity API保持相同
3.3 添加CORS允许客户端凭证
builder.Services.AddCors(options => { options.AddPolicy("AllowClient", policy => { policy.WithOrigins("https://localhost:xxxx") // 客户端应用地址 .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); });
3.4 启用CORS中间件
在UseHttpsRedirection之后添加:
app.UseCors("AllowClient");
关键注意事项
- HTTPS强制:
SameSite=None要求Cookie必须是Secure,因此所有应用必须启用HTTPS(本地开发可使用.NET的开发证书)。 - 域名统一:本地开发使用
localhost作为统一域名,生产环境需使用相同的根域名(如example.com,子域名identity.example.com、client.example.com等)。 - 密钥目录权限:确保三个应用都有权限访问
c:\temp-keys目录,否则数据保护无法读取/写入密钥。
内容的提问来源于stack exchange,提问作者Jon Sowers
相关产品推荐
相关产品推荐

