Почему спецификация фронтального канала не работает? - PullRequest
1 голос
/ 22 апреля 2020

У нас есть следующая среда:

  • Реализация IdentityServer4 в качестве поставщика услуг идентификации.
  • 2 Сайты, созданные на основе. Net Core 2.2.8 и интегрированные с Identity .
  • Локальный IIS 10.0.18362.1 на моем ноутбуке Windows 10 Pro

Конфигурация клиента на IdentityServer:

            new Client
            {
                ClientName = "App1",
                ClientId = "App1",
                AllowedGrantTypes = GrantTypes.Hybrid,
                RequireConsent = false,
                RedirectUris = "https://localhost:5000/siging-oidc",
                PostLogoutRedirectUris = "https://localhost:5000/home/index",
                FrontChannelLogoutUri = "https://localhost:5000/home/frontchannellogout",
                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile
                },
                ClientSecrets =
                {
                    new Secret("secretApp1".Sha256())
                }
            },
            new Client
            {
                ClientName = "App2",
                ClientId = "App2",
                AllowedGrantTypes = GrantTypes.Hybrid,
                RequireConsent = false,
                RedirectUris = "https://localhost:5001/siging-oidc",
                PostLogoutRedirectUris = "https://localhost:5000/home/index",
                FrontChannelLogoutUri = "https://localhost:5001/home/frontchannellogout",
                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile
                },
                ClientSecrets =
                {
                    new Secret("secretApp2".Sha256())
                }
            }

Конфигурация клиента на стороне клиента:

        //For authentication App1
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.Authority = "https://localhost:44329/";
            options.ClientId = "App1";
            options.ResponseType = "code id_token";
            options.Scope.Add(OidcConstants.StandardScopes.OpenId);
            options.Scope.Add(OidcConstants.StandardScopes.Profile);
            options.SaveTokens = true;
            options.ClientSecret = "secretApp1";
            options.GetClaimsFromUserInfoEndpoint = true;

            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = JwtClaimTypes.GivenName
            };

            options.ClaimActions.Remove("acr");

            options.Events = new OpenIdConnectEvents
            {
                OnRedirectToIdentityProvider = n =>
                {
                    if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        n.ProtocolMessage.AcrValues = $"tenant:{configuration["TenantId"]}";
                    }
                    return Task.FromResult(0);
                },
                OnRemoteFailure = context =>
                {
                    context.Response.Redirect("/home");
                    context.HandleResponse();

                    return Task.FromResult(0);
                }
            };
        });

        //For authentication App2
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.Authority = "https://localhost:44329/";
            options.ClientId = "App2";
            options.ResponseType = "code id_token";
            options.Scope.Add(OidcConstants.StandardScopes.OpenId);
            options.Scope.Add(OidcConstants.StandardScopes.Profile);
            options.SaveTokens = true;
            options.ClientSecret = "secretApp2";
            options.GetClaimsFromUserInfoEndpoint = true;

            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = JwtClaimTypes.GivenName
            };

            options.ClaimActions.Remove("acr");

            options.Events = new OpenIdConnectEvents
            {
                OnRedirectToIdentityProvider = n =>
                {
                    if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        n.ProtocolMessage.AcrValues = $"tenant:{configuration["TenantId"]}";
                    }
                    return Task.FromResult(0);
                },
                OnRemoteFailure = context =>
                {
                    context.Response.Redirect("/home");
                    context.HandleResponse();

                    return Task.FromResult(0);
                }
            };
        });

Это реализации Logout для обоих приложений:

        // Logout App1
        public async Task<IActionResult> Logout()
    {
        if (User.Identity.IsAuthenticated)
        {
            var idToken = await HttpContext.GetTokenAsync("id_token");
            HttpContext.Session.Clear();
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            var disco = await _discoveryCache.GetAsync();
            var postLogoutUrl = "https://localhost:5001/home/index";// I want to go to App2 when I logout from App1
            var urlEndSession = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(idToken, postLogoutUrl);
            return Redirect(urlEndSession);
        }

        return View("Index");
    }

        // Logout App2
        public async Task<IActionResult> Logout()
    {
        if (User.Identity.IsAuthenticated)
        {
            var idToken = await HttpContext.GetTokenAsync("id_token");
            HttpContext.Session.Clear();
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            var disco = await _discoveryCache.GetAsync();
            var postLogoutUrl = "https://localhost:5000/home/index"; // I want to go to App1 when I logout from App2
            var urlEndSession = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(idToken, postLogoutUrl);
            return Redirect(urlEndSession);
        }

        return View("Index");
    }

Это реализации FrontChannelLogout для обоих приложений:

    [Authorize]
    public async Task<IActionResult> Frontchannellogout(string sid)
    {
        if (User.Identity.IsAuthenticated)
        {
            var currentSid = User.FindFirst("sid")?.Value ?? "";
            if (string.Equals(currentSid, sid, StringComparison.Ordinal))
            {
                HttpContext.Session.Clear();
                await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            }
        }

        return NoContent();
    }

Мы работаем над выполнением sh, что когда пользователь выходит из одного приложения, он фактически выходит из всей системы, используя спецификацию фронтального канала.

Как только вы входите в приложение App1 и переходите в приложение App2, у нас есть SSO и пользователь вошел в оба приложения. Как только пользователь решит выйти из текущего приложения, он должен выйти из другого приложения и Identity Server.

Приложение (App1), из которого я вышел, удалило куки, как это должно было произойти, и куки-файлы identityServer также были удалены, но другое приложение (App2) не удаляло свои куки-файлы и никогда не запускало свой frontchannellogout, поэтому я все еще вхожу в это второе приложение (App2).

I Мне известно о недавних изменениях, связанных с развертыванием Google, связанных с параметром SameSite cook ie, и я также получил обновленную информацию, связанную с недавним откатом этих изменений, и даже подумал, что не уверен на 100%, что является причиной моей проблемы, я думаю, это может быть связано с этим. Из-за этого я попытался обойти несколько решений, которые я нашел в Интернете, но пока у меня ничего не получилось,

Я использовал это Предстоящий SameSite Cook ie Изменения в ASP. NET и ASP. NET Core в качестве руководства для решения моей проблемы. Это мой код для классов StartUp.cs App1 и App2:

        private void CheckSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None)
        {
            var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
            if (userAgent.Contains("Chrome/8") || userAgent.Contains("Firefox/7"))
            {
                options.SameSite = (SameSiteMode)(-1);
            }
        }
    }

        public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = (SameSiteMode)(-1);
            options.OnAppendCookie = cookieContext =>
                CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
            options.OnDeleteCookie = cookieContext =>
                CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
        });

        // ... some configurations more like services.AddAuthentication(...) ...
    }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // ... 

        app.UseCookiePolicy();
        app.UseAuthentication();

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

РЕДАКТИРОВАТЬ: браузер готовить ie хранилище

1-) войти в App1

log-in into App1

2-) войти в App2

log-in into App2

3-) после нажатия первый шаг из App2

after hit logout from App2 first-step

4-) после перенаправления identityserver на App1, поскольку postlogoutredirecturi - это App1 в App2

after identityserver redirect to App1 because the postlogoutredirecturi is App1 in App2

...