ASP.NET 身份 Cookie 未在具有不同端口的 localhost 上设置

ASP.NET Identity Cookie not being set on localhost with different ports

提问人:jacklohse 提问时间:11/3/2023 更新时间:11/3/2023 访问量:26

问:

我有两个应用程序(带有 Angular 的 .NET 7),都在 localhost 上,但端口不同

应用 1:这是一个共享的 Web API,它充当我的其他应用程序的单一登录点。
应用程序 2:这是主应用程序,但它调用应用程序 1 的 API 进行登录。

这两个应用都使用 Visual Studio 提供的带有 Angular 模板的 ASP.NET Core

目标:用户可以进入应用程序 2 并通过输入他们的用户名和密码来发起登录请求。单击登录后,这些凭据将发送到应用程序 1 以处理身份验证,然后应用程序 1 发送身份验证 cookie


我的问题:从应用程序 2 到应用程序 1 的登录请求有效,用户数据在应用程序 2 中返回,并发送身份验证 cookie。但是,Set-Cookie 标头实际上并没有将 cookie 保存到浏览器,特别是针对 localhost,因此用户不会进行身份验证。Postman 中的相同请求有效,并且 cookie 将保存到两个应用程序,并且用户会进行身份验证。


我认为正在发生的事情:我认为这可能与 chrome 处理特殊域 localhost 的方式有关。我可以得到这个完全相同的请求,在没有缺陷的情况下在 Postman 上工作。这些应用位于不同的端口上:应用 1:https://localhost:44465,应用 2:https://localhost:5000。我在网上阅读了相当多的文章,说因为它们位于不同的端口上,所以 chrome 会阻止 cookie 被共享。

APP 1 已编辑程序 .cs

var builder = WebApplication.CreateBuilder(args);

string? envRaw = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
string env = envRaw is null ? "Development" : envRaw;

var Config = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: true)
        .AddEnvironmentVariables().Build();

builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy",
        policy =>
        {
            policy.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
        });
});

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("SHARED DIRECTORY"))
    .SetApplicationName("APP_NAME")
    .SetDefaultKeyLifetime(TimeSpan.FromDays(7));

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
}).AddIdentityCookies();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = "APP_NAME";
    options.Cookie.Path = "/";
    options.Cookie.Domain = null;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.None;

    options.Events = new CookieAuthenticationEvents()
    {
        OnRedirectToLogin = ctx =>
        {
            var isApiCall = ctx.Request.Path.StartsWithSegments("/api");
            var hasOkStatus = ctx.Response.StatusCode == (int)HttpStatusCode.OK;
            if (isApiCall && hasOkStatus)
            {
                ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
            else
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
            return Task.FromResult<object>(null);
        },
        // Do not redirect to /AccessDenied for forbidden API call; return Forbidden status code instead.
        OnRedirectToAccessDenied = ctx =>
        {
            var isApiCall = ctx.Request.Path.StartsWithSegments("/api");
            var hasOkStatus = ctx.Response.StatusCode == (int)HttpStatusCode.OK;
            if (isApiCall && hasOkStatus)
            {
                ctx.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
            else
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
            return Task.FromResult<object>(null);
        }
    };
}); 

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}"
).RequireCors("CorsPolicy");

app.MapDefaultControllerRoute();

app.MapFallbackToFile("index.html");

app.Run();

APP 2 已编辑程序 .cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
    options.AddPolicy("CorsPolicy", builder =>
        builder.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader()
    )
);


// ASP.Net Autentication
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("SHARED DIRECTORY"))
    .SetApplicationName("APP_NAME")
    .SetDefaultKeyLifetime(TimeSpan.FromDays(7));

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
}).AddIdentityCookies();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = "APP_NAME";
    options.Cookie.Path = "/";
    options.Cookie.Domain = null;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.None;

    options.Events = new CookieAuthenticationEvents()
    {
        OnRedirectToLogin = ctx =>
        {
            var isApiCall = ctx.Request.Path.StartsWithSegments("/api");
            var hasOkStatus = ctx.Response.StatusCode == (int)HttpStatusCode.OK;
            if (isApiCall && hasOkStatus)
            {
                ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
            else
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
            return Task.FromResult<object>(null);
        },
        OnRedirectToAccessDenied = ctx =>
        {
            var isApiCall = ctx.Request.Path.StartsWithSegments("/api");
            var hasOkStatus = ctx.Response.StatusCode == (int)HttpStatusCode.OK;
            if (isApiCall && hasOkStatus)
            {
                ctx.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
            else
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
            return Task.FromResult<object>(null);
        }
    };
});

var app = builder.Build();
var cookiePathBase = builder.Configuration["App:CookiePathBase"];
app.UsePathBase(cookiePathBase);

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString(cookiePathBase);
    return next();
});


app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.Run();

在此处
输入图像描述 从 app2 到 app1 的请求返回 200 OK 的图片 在此处输入图像描述 200 OK
响应
中包含的身份验证 cookie 的图片 在此处

输入图像描述 在 Postman 中正确设置 Cookie

我几乎尝试了网上能找到的所有东西。所以这里是我尝试过的事情的清单,但没有一个改变任何东西:

  • 将 Cookie 域指定为 “localhost”、null、“”,并完全省略它

  • 我将 cookie 选项设置为 SameSite=None,并将安全策略设置为“一如既往”。我还尝试了宽松和严格的 SameSite 模式

  • 我尝试使用 proxy.conf.js 将对 localhost:44465 的请求代理为 localhost:5000,希望这会绕过跨站点 cookie

  • 将 Access-Control-Allow-Origin 设置为特定端口,而不是通配符值 (*)

  • 将 Access-Control-Allow-Credentials 设置为 true

  • 将 Cookie 设置为 HttpOnly,并为其指定会话以外的到期日期

  • 将每个应用程序中的 Cookie 名称更改为唯一

  • 尝试过允许 CORS 的 Chrome 扩展程序

这些都没有产生任何结果。我的下一个尝试是将 IIS 主机配置为具有 localhost 的子域来镜像生产环境,例如。app1.localhost 和 app2.localhost。

期待听到你们的建议!

asp.net asp.net-core 身份验证 cookie asp.net-identity

评论

0赞 pcalkins 11/4/2023
APP2 必须设置该会话 cookie。从 APP1 设置的 Cookie(具有不同端口的本地主机被视为不同的域)不会随发送到 APP2 的请求一起发送。samesite 属性完全不会影响这一点。(这与请求的来源有关......而不是请求目标是否与设置cookie的域匹配) 还有浏览器强制执行的 CORS 规则。Postman 对跨域请求没有此类限制。(无论如何,您可以通过将浏览器发送到 APP1 的 URL 来验证 cookie 是否已设置。然后检查 dev-tools 中是否有 cookie。

答: 暂无答案