C# (ядро dotnet) Корреляция не удалась. Нагрузочный балансировщик на входе (GLBC) на GKE
Я боролся с аутентификацией в течение нескольких недель в проекте, и это убивает меня. Потерпи меня, это мой первый пост на stackru.
Я искал и гуглял / искал здесь на stackru, и придумал несколько разных вещей, но, похоже, ничего не помогло:
- Разместите ASP.NET Core в веб-ферме ( https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/web-farm?view=aspnetcore-2.1).
- Настройте ядро ASP.NET для работы с прокси-серверами и балансировщиками нагрузки ( https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1).
- Аналогичная проблема с github ( https://github.com/aspnet/Security/issues/1755)
Моя проверка подлинности Microsoft Azure имеет ту же проблему и основана на: https://github.com/microsoftgraph/aspnetcore-connect-sample
Трассировки стека:
[09:58:48 INF] Error from RemoteAuthentication: Correlation failed..
[09:58:48 ERR] An unhandled exception has occurred while executing the request.
System.Exception: An error was encountered while handling the remote login.
---> System.Exception: Correlation failed.
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler1.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Muninn.Startup.<>c.<<Configure>b__8_0>d.MoveNext()
in /Muninn/Startup.cs:line 180 --- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
Это Startup.cs: открытый класс Startup { public Startup(конфигурация IConfiguration) { Configuration = configuration; }
public IConfiguration Configuration { get; }
public const string ObjectIdentifierType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
public const string TenantIdType = "http://schemas.microsoft.com/identity/claims/tenantid";
public readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddGoogle(googleOptions =>
{
googleOptions.Scope.Add(CalendarService.Scope.Calendar);
googleOptions.ClientId = Configuration["Google:ClientId"];
googleOptions.ClientSecret = Configuration["Google:ClientSecret"];
googleOptions.AccessType = "offline";
googleOptions.SaveTokens = true;
googleOptions.CallbackPath = "/signin-google";
googleOptions.Events = new OAuthEvents()
{
OnCreatingTicket = async (context) =>
{
var userEmail = context.Identity.FindFirst(ClaimTypes.Email).Value;
Log.Information("New user logged in with Google: " + userEmail);
if(string.IsNullOrEmpty(context.AccessToken))
Log.Error("Access token was null");
if (string.IsNullOrEmpty(context.RefreshToken))
Log.Error("Refresh token was null");
var tokenResponse = new TokenResponse()
{
AccessToken = context.AccessToken,
RefreshToken = context.RefreshToken,
ExpiresInSeconds = (long)context.ExpiresIn.Value.TotalSeconds,
IssuedUtc = DateTime.UtcNow
};
tokenResponse.Scope = CalendarService.Scope.Calendar;
await dataStore.StoreAsync(userEmail, tokenResponse);
Log.Information("User has been saved to the system: " + userEmail);
}
};
})
.AddCookie(options =>
{
options.LoginPath = "/Account/SignIn";
options.LogoutPath = "/Account/SignOff";
});
services.AddMvc();
var redis = ConnectionMultiplexer.Connect(Configuration["Redis:DefaultConnection"]);
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys")
.SetApplicationName("myapp");
services.AddDistributedRedisCache(options =>
{
options.Configuration = Configuration["Redis:DefaultConnection"];
options.InstanceName = "master";
});
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSession(options =>
{
options.Cookie.Domain = ".myapp.com";
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
options.Cookie.Name = ".myapp.Session";
options.IdleTimeout = TimeSpan.FromSeconds(5);
});
services.AddSingleton<IGraphAuthProvider, GraphAuthProvider>();
services.AddTransient<IGraphSdkHelper, GraphSdkHelper>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<ICalendarActions, CalendarActions>();
services.AddTransient<IOutlookCommunication, OutlookCommunication>();
services.Configure<GoogleAuthOptions>(Configuration.GetSection("Google"));
services.AddTransient<IGoogleCommunication, GoogleCommunication>();
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart
});
services.AddAntiforgery();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.Use(async (context, next) =>
{
context.Request.Scheme = "https";
await next.Invoke();
});
app.UseHttpsRedirection();
var forceSSL = new RewriteOptions()
.AddRedirectToHttps();
app.UseRewriter(forceSSL);
app.UseStaticFiles();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Все это выполняется в GKE как развертывание, где файл dockerfile:
FROM microsoft/dotnet:2.1-sdk as builder
COPY . .
RUN dotnet restore && dotnet build
WORKDIR myapp
RUN dotnet publish --output /app --configuration release -r ubuntu.16.04-x64
FROM microsoft/dotnet:2.1-runtime
EXPOSE 5000/tcp
ENV ASPNETCORE_URLS http://*:5000
COPY --from=builder /app .
ENTRYPOINT dotnet myapp.dll
Я подозреваю, что проблема заключается в том, что обратный вызов конечной точки google/azure не расшифрован должным образом, потому что мое приложение не осознает, что оно размещено в "веб-ферме" в Кубернетесе, несмотря на то, что мой кэш redis имеет "DataProtection-Keys", когда я выполняю в контейнере,
Приложение также должно понимать, что его контекст - https://myapp.com/ поскольку я установил бит ForwardHeaders.
Помогите, пожалуйста..?
Основное приложение dr .NET, стоящее за GLBC, получает "System.Exception: Корреляция не удалась". несмотря на использование AddDataProtection() и перенаправленных заголовков. Я в ярости, пожалуйста, помогите.
1 ответ
Поскольку на этот вопрос не было ответа, я создал проблему в репозитории Aspnet/Security на github ( https://github.com/aspnet/Security/issues/1844), где некоторые действительно хорошие сопровождающие люди нашли время, чтобы помочь.
Решение проблемы выше было следующим:
Приложение полностью настроено на https (context.Request.Scheme = "https"), но уровень балансировки нагрузки пропускал некоторые http-запросы.
Когда cookie-файл был создан через http, он в основном ложил приложение, что трафик был https, и это вызывало ошибку корреляции.