OAuth. NET Core - для многих перенаправлений после входа в систему с пользовательскими параметрами - Multi Tenant - PullRequest
1 голос
/ 23 апреля 2020

Я пытаюсь реализовать OAuth для каждого арендатора. У каждого арендатора есть свои OAuthOptions. Я перезаписал OAuthOptions и с помощью IOptionsMonitor каждый раз разрешаю OAuthOptions. Основываясь на этом ответе: openid connect - идентификация арендатора во время входа в систему

Но прямо сейчас, после входа в систему по внешнему интерфейсу, обратный вызов для входа заканчивается многими перенаправлениями.

Вход выполнен успешно -> перенаправление в приложение -> приложение говорит, что не аутентифицировано -> перенаправление на внешний вход в систему -> повторить.

Код:

My Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();

    services.AddAuthentication(options =>

        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "OAuth";
    })
    .AddCookie("OAuth.Cookie", options =>
    {
        options.Cookie.Name = "OAuth-cookiename";
        options.Cookie.SameSite = SameSiteMode.None;

        options.LoginPath = "/account/login";
        options.AccessDeniedPath = "/account/login";
    })
    .AddOAuth<MyOptions, OAuthHandler<MyOptions>>("OAuth", options =>
    {
        // All options are set at runtime by tenant settings
    });

    services.AddScoped<IOptionsMonitor<MyOptions>, MyOptionsMonitor>();
    services.AddScoped<IConfigureOptions<MyOptions>, ConfigureMyOptions>();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Startup.Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

ConfigureMyOptions.cs

    public class ConfigureMyOptions : IConfigureNamedOptions<MyOptions>
{
    private HttpContext _httpContext;
    private IDataProtectionProvider _dataProtectionProvider;
    private MyOptions myCurrentOptions;

    public ConfigureMyOptions(IHttpContextAccessor contextAccessor, IDataProtectionProvider dataProtectionProvider)
    {
        _httpContext = contextAccessor.HttpContext;
        _dataProtectionProvider = dataProtectionProvider;
    }

    public void Configure(string name, MyOptions options)
    {
        //var tenant = _httpContext.ResolveTenant();

        // in my code i use tenant.Settings for these:

        options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
        options.TokenEndpoint = "https://github.com/login/oauth/access_token";
        options.UserInformationEndpoint = "https://api.github.com/user";

        options.ClientId = "redacted";
        options.ClientSecret = "redacted";

        options.Scope.Add("openid");
        options.Scope.Add("write:gpg_key");
        options.Scope.Add("repo");
        options.Scope.Add("read:user");

        options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
        options.ClaimActions.MapJsonKey("External", "id");

        options.SignInScheme = "OAuth.Cookie";
        options.CallbackPath = new PathString("/signin");

        options.SaveTokens = true;

        options.Events = new OAuthEvents
        {
            OnCreatingTicket = _onCreatingTicket,
            OnTicketReceived = _onTicketReceived
        };

        myCurrentOptions = options;
    }

    public void Configure(MyOptions options) => Configure(Options.DefaultName, options);

    private static async Task _onCreatingTicket(OAuthCreatingTicketContext context)
    {
        // Get the external user id and set it as a claim
        using (var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint))
        {
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

            using (var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted))
            {
                response.EnsureSuccessStatusCode();
                var user = JObject.Parse(await response.Content.ReadAsStringAsync());

                context.RunClaimActions(user);
            }
        }
    }

    private static Task _onTicketReceived(TicketReceivedContext context)
    {
        context.Properties.IsPersistent = true;
        context.Properties.AllowRefresh = true;
        context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30);

        return Task.CompletedTask;
    }
}

MyOptions:

    // Overwritten to by pass validate
    public class MyOptions : OAuthOptions
    {
        public override void Validate()
        {
            return;
        }

        public override void Validate(string scheme)
        {
            return;
        }
    }

MyOptionsMonitor:

    // TODO caching
    public class MyOptionsMonitor : IOptionsMonitor<MyOptions>
    {
        //     private readonly TenantContext<Tenant> _tenantContext;
        private readonly IOptionsFactory<MyOptions> _optionsFactory;

        public MyOptionsMonitor(
            //  TenantContext<Tenant> tenantContext,
            IOptionsFactory<MyOptions> optionsFactory)
        {
            //      _tenantContext = tenantContext;
            _optionsFactory = optionsFactory;
        }

        public MyOptions CurrentValue => Get(Options.DefaultName);

        public MyOptions Get(string name)
        {
            return _optionsFactory.Create($"{name}");
        }

        public IDisposable OnChange(Action<MyOptions, string> listener)
        {
            return null;
        }
    }
...