C# (ядро dotnet) Корреляция не удалась. Нагрузочный балансировщик на входе (GLBC) на GKE

Я боролся с аутентификацией в течение нескольких недель в проекте, и это убивает меня. Потерпи меня, это мой первый пост на stackru.

Я искал и гуглял / искал здесь на stackru, и придумал несколько разных вещей, но, похоже, ничего не помогло:

Моя проверка подлинности 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, и это вызывало ошибку корреляции.

Другие вопросы по тегам