У меня возникла проблема с моим приложением .NET 6: файлы cookie корреляции теряются после аутентификации при развертывании за обратным прокси-сервером. Обратный прокси-сервер обрабатывает HTTPS, поэтому само приложение не управляет HTTPS напрямую.
Приложение работает правильно при локальном запуске, но в производственной среде (за обратным прокси-сервером) корреляция файлы cookie теряются после аутентификации.
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
string SecurityLevelClaimPolicy = "SecurityLevelClaimPolicy";
string SecurityLevelClaim = "Claims";
Settings _settings = new Settings();
_settings.IdConfiguration = new IdConfiguration();
//_settings.ClientType = ClientType.ApiAccess;
_settings.ClientType = ClientType.ApiAccessForMultiTenantClient;
_settings.ApiAudience1 = "URL";
_settings.IdConfiguration = new IdConfiguration();
_settings.IdConfiguration.ClientId = "clientid";
_settings.IdConfiguration.RsaPrivateKeyJwk = new TestIT.Authenticator.API.Helpers.SecurityKey("{'d':'XXXXXX','kid':'XXXXXX','use':'XXXXX','alg':'XXXXX'}", "XXXXX");
_settings.IdConfiguration.Scope = "scope";
_settings.IdConfiguration.StsUrl = "URL";
builder.Services.AddHttpContextAccessor();
builder.Services.AddMvc()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = new LowercaseContractResolver();
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.Converters.Add(new TestIT.Authenticator.API.Helpers.DateTimeHelper("dd.MM.yyyyTHH.mm.ss"));
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts =>
{
opts.ResourcesPath = "Resources";
})
.AddDataAnnotationsLocalization();
builder.Services.AddControllers();
builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddEndpointsApiExplorer();
builder.Services.Configure(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always; // Ensure cookies are marked as Secure
});
// Create a settings instance. These can be injected into other objects, for instance the HomeController
builder.Services.AddSingleton(_settings);
// We need the IdConfiguration instance as a service as well:
builder.Services.AddSingleton(_settings.IdConfiguration);
// Services for calculating the expiration time for tokens
var dateTimeService = new DateTimeService();
builder.Services.AddSingleton(dateTimeService);
builder.Services.AddTransient();
builder.Services.AddSingleton();
// No request object is needed, so we inject a null object for payload claims creation instead
builder.Services.AddSingleton(new NullPayloadClaimsCreatorForRequestObjects());
//var clientAssertionPayloadClaimsCreator = new ClientAssertionPayloadClaimsCreator(dateTimeService);
//builder.Services.AddSingleton(clientAssertionPayloadClaimsCreator);
var clientAssertionPayloadClaimsCreator = new ClientAssertionPayloadClaimsCreator(dateTimeService);
// We need payload claims for the token request, both the "default" type and for the multi-tenant organization number:
var compositePayloadClaimsCreator = new CompositePayloadClaimsCreator(new List
{
clientAssertionPayloadClaimsCreator,
new PayloadClaimsCreatorForMultiTenantClient()
});
// We add this object as an instance of IPayloadClaimsCreatorForClientAssertion
builder.Services.AddSingleton(compositePayloadClaimsCreator);
builder.Services.AddTransient();
builder.Services.AddSingleton();
builder.Services.AddTransient();
// Builder for client assertions payloads
// Builder for JWT tokens used for client assertions
builder.Services.AddSingleton(new DiscoveryDocumentGetter(_settings.IdConfiguration.StsUrl));
builder.Services.AddSingleton();
// Builds token requests (in our case, refresh token requests)
builder.Services.AddTransient();
// Register services here
builder.Services.AddTransient();
builder.Services.AddScoped();
// A getter of user session data, uses the user session data store
builder.Services.AddTransient();
builder.Services.AddAuthentication(options =>
{
// Configure default authentication schemes for the application
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
//options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure JWT bearer authentication
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("Secret"))),
ValidateIssuer = true,
ValidIssuer = config.GetValue("Issuer"),
ValidateAudience = true,
ValidAudience = config.GetValue("Audience"),
ValidateLifetime = true,
};
});
builder.Services.AddSwaggerGen(c =>
{
// Configure Swagger documentation
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestIt .Net middleware", Version = "v1" });
// Add JWT bearer authentication to Swagger
var securityScheme = new OpenApiSecurityScheme
{
Name = "Authorization",
BearerFormat = "JWT",
Scheme = "bearer",
Description = "JWT Authorization header using the Bearer scheme.",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
};
c.AddSecurityDefinition("Bearer", securityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty()
}
});
});
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Ensure cookies are sent over HTTPS
options.Cookie.SameSite = SameSiteMode.None; // Set the SameSite attribute to None
});
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.Configure(OpenIdConnectOptions =>
{
OpenIdConnectOptions.MinimumSameSitePolicy = SameSiteMode.None;
OpenIdConnectOptions.Secure = CookieSecurePolicy.Always; // Ensure cookies are marked as Secure
});
//// Set authentication options (these will call the AuthenticationOptionsInitializer and OpenIdConnectOptionsInitializer instances)
builder.Services.AddAuthentication()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectOptions =>
{
// Path to access denied endpoint. Used when authorization fails
OpenIdConnectOptions.AccessDeniedPath = "/authorization/access-denied";
OpenIdConnectOptions.AccessDeniedPath = "/authorization/access-denied";
// Ensure cookies are only sent over HTTPS
OpenIdConnectOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always;
// Set SameSite policy. Adjust as necessary based on your requirements
OpenIdConnectOptions.Cookie.SameSite = SameSiteMode.None;
// Mark the cookie as HttpOnly
OpenIdConnectOptions.Cookie.HttpOnly = true;
})
.AddOpenIdConnect(openIdConnectOptions =>
{
// We need to extract the OpenID Connect options initializer from the service provider:
var serviceProvider = builder.Services.BuildServiceProvider();
var initializer = serviceProvider.GetService();
initializer!.Configure(nameof(UserAuthendication), openIdConnectOptions);
openIdConnectOptions.CallbackPath = "/signin-oidc";
openIdConnectOptions.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
openIdConnectOptions.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
});
var securityLevelClaimPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim(IdSecurityLevelClaim, "4")
.Build();
builder.Services.AddDataProtection();
builder.Services.AddAuthorization(config =>
{
config.AddPolicy(SecurityLevelClaimPolicy, securityLevelClaimPolicy);
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseRouting();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
});
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.Always,
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Есть ли что-то, что мне не хватает в конфигурации, что может привести к потере корреляционных файлов cookie после аутентификации? Будем очень признательны за любые советы и идеи.
Изменить: забыл упомянуть, что приложение находится внутри контейнера докеров
У меня возникла проблема с моим приложением .NET 6: файлы cookie корреляции теряются после аутентификации при развертывании за обратным прокси-сервером. Обратный прокси-сервер обрабатывает HTTPS, поэтому само приложение не управляет HTTPS напрямую. Приложение работает правильно при локальном запуске, но в производственной среде (за обратным прокси-сервером) корреляция файлы cookie теряются после аутентификации. [code] var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build();
builder.Services.AddControllers(); builder.Services.AddRouting(options => options.LowercaseUrls = true); builder.Services.AddEndpointsApiExplorer(); builder.Services.Configure(options => { options.MinimumSameSitePolicy = SameSiteMode.None; options.Secure = CookieSecurePolicy.Always; // Ensure cookies are marked as Secure });
// Create a settings instance. These can be injected into other objects, for instance the HomeController builder.Services.AddSingleton(_settings); // We need the IdConfiguration instance as a service as well: builder.Services.AddSingleton(_settings.IdConfiguration);
// Services for calculating the expiration time for tokens var dateTimeService = new DateTimeService(); builder.Services.AddSingleton(dateTimeService); builder.Services.AddTransient(); builder.Services.AddSingleton();
// No request object is needed, so we inject a null object for payload claims creation instead builder.Services.AddSingleton(new NullPayloadClaimsCreatorForRequestObjects());
//var clientAssertionPayloadClaimsCreator = new ClientAssertionPayloadClaimsCreator(dateTimeService); //builder.Services.AddSingleton(clientAssertionPayloadClaimsCreator);
var clientAssertionPayloadClaimsCreator = new ClientAssertionPayloadClaimsCreator(dateTimeService);
// We need payload claims for the token request, both the "default" type and for the multi-tenant organization number: var compositePayloadClaimsCreator = new CompositePayloadClaimsCreator(new List { clientAssertionPayloadClaimsCreator, new PayloadClaimsCreatorForMultiTenantClient() }); // We add this object as an instance of IPayloadClaimsCreatorForClientAssertion builder.Services.AddSingleton(compositePayloadClaimsCreator);
builder.Services.AddTransient();
builder.Services.AddSingleton();
builder.Services.AddTransient(); // Builder for client assertions payloads // Builder for JWT tokens used for client assertions builder.Services.AddSingleton(new DiscoveryDocumentGetter(_settings.IdConfiguration.StsUrl)); builder.Services.AddSingleton(); // Builds token requests (in our case, refresh token requests) builder.Services.AddTransient(); // Register services here
builder.Services.AddTransient(); builder.Services.AddScoped(); // A getter of user session data, uses the user session data store builder.Services.AddTransient();
builder.Services.AddSwaggerGen(c => { // Configure Swagger documentation c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestIt .Net middleware", Version = "v1" });
// Add JWT bearer authentication to Swagger var securityScheme = new OpenApiSecurityScheme { Name = "Authorization", BearerFormat = "JWT", Scheme = "bearer", Description = "JWT Authorization header using the Bearer scheme.", In = ParameterLocation.Header, Type = SecuritySchemeType.Http, }; c.AddSecurityDefinition("Bearer", securityScheme); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty() } }); });
builder.Services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Ensure cookies are sent over HTTPS options.Cookie.SameSite = SameSiteMode.None; // Set the SameSite attribute to None });
builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.Configure(OpenIdConnectOptions => { OpenIdConnectOptions.MinimumSameSitePolicy = SameSiteMode.None; OpenIdConnectOptions.Secure = CookieSecurePolicy.Always; // Ensure cookies are marked as Secure }); //// Set authentication options (these will call the AuthenticationOptionsInitializer and OpenIdConnectOptionsInitializer instances) builder.Services.AddAuthentication() .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectOptions => { // Path to access denied endpoint. Used when authorization fails OpenIdConnectOptions.AccessDeniedPath = "/authorization/access-denied"; OpenIdConnectOptions.AccessDeniedPath = "/authorization/access-denied";
// Ensure cookies are only sent over HTTPS OpenIdConnectOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always;
// Set SameSite policy. Adjust as necessary based on your requirements OpenIdConnectOptions.Cookie.SameSite = SameSiteMode.None;
// Mark the cookie as HttpOnly OpenIdConnectOptions.Cookie.HttpOnly = true; }) .AddOpenIdConnect(openIdConnectOptions => { // We need to extract the OpenID Connect options initializer from the service provider: var serviceProvider = builder.Services.BuildServiceProvider(); var initializer = serviceProvider.GetService(); initializer!.Configure(nameof(UserAuthendication), openIdConnectOptions); openIdConnectOptions.CallbackPath = "/signin-oidc"; openIdConnectOptions.NonceCookie.SecurePolicy = CookieSecurePolicy.Always; openIdConnectOptions.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
});
var securityLevelClaimPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireClaim(IdSecurityLevelClaim, "4") .Build();
app.Run(); [/code] Есть ли что-то, что мне не хватает в конфигурации, что может привести к потере корреляционных файлов cookie после аутентификации? Будем очень признательны за любые советы и идеи. Изменить: забыл упомянуть, что приложение находится внутри контейнера докеров