为什么前道规范不行?

问题描述 投票:1回答:1

我们有以下环境。

  • 一个IdentityServer4实现作为身份服务提供者。
  • 2个建立在.Net Core 2.2.8上的网站,并与Identity集成。
  • 我的笔记本Windows 10 Pro上的本地IIS 10.0.18362.1。

客户端在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();
    }

我们正在努力实现,当用户从一个App中签出时,实际上是通过前台规范从整个系统中签出。

一旦你在App1中登录,然后移动到App2中,我们就有了SSO,用户已经在两个App中登录了。一旦用户决定从当前应用中注销,他也应该从其他应用和身份服务器中注销。

我注销的应用(App1)按规定删除了cookie,identityServer的cookie也被删除了,但另一个应用(App2)没有删除cookie,也没有点击前台注销,所以我仍然在第二个应用(App2)中登录。

我知道最近Google推出的与SameSite cookie选项有关的改变,我也更新了与最近回滚这一改变有关的信息,即使我不是100%确定那是我的问题的原因,我想它可能与此有关。 正因为如此,我已经尝试了一些我在网上找到的解决方法,但到目前为止我还没有成功。

我用了这个 ASP.NET和ASP.NET Core中即将到来的SameSite Cookie变更 作为解决我的问题的指南。这是我在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?}");
        });
    }

编辑:浏览器cookie存储

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在App2中是App1。

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

c# asp.net-core cookies identityserver4 samesite
1个回答
0
投票

我得到了一些解决方案。

在IdentityServer内部的AccountControllerLogout Action中,我返回了一个重定向到postlogoutredirecturi,就像这样。

public class AccountController : Controller
{        
//... code before this action

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Logout(LogoutInputModel model)
    {
        var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

        if (User?.Identity.IsAuthenticated == true)
        {
            await HttpContext.SignOutAsync();

            await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
        }

        if (vm.TriggerExternalSignout)
        {
            string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

            return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
        }

        // this line was here before I got the problem and I chenged later.
        return Redirect(vm.PostLogoutRedirectUri);
    }

//... code after this action
 }

所以,我只是修改了最后一行,返回LoggedOut视图,其中一个iframe是由LogoutInputModel vm的SignOutIframeUrl属性渲染的。

public class AccountController : Controller
{        
//... code before this action

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Logout(LogoutInputModel model)
    {
        var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

        if (User?.Identity.IsAuthenticated == true)
        {
            await HttpContext.SignOutAsync();

            await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
        }

        if (vm.TriggerExternalSignout)
        {
            string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

            return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
        }

        // this line is the one solve my problem.
        return View("LoggedOut", vm);
    }

//... code after this action
 }

这是LoggedOut视图的代码。

@model LoggedOutViewModel
@{
  // set this so the layout rendering sees an anonymous user
  ViewData["signed-out"] = true;
}
<div id="main-panel" class="mt-5">
    <div class="page-header logged-out text-center">
        <div id="branding-area">
            <img src="~/some_image.svg" alt="gov-easy" class="logo" />
        </div>
        <p class="display-4">You are now being logged out...</p>
        <p>If you are not automatically redirected, click the button below.</p>
        <button class="btn btn-primary mx-auto" id="returnHomeBtn">Return home page</button>
    @if (Model.PostLogoutRedirectUri != null)
    {
        <div class="hidden">
            Click <a class="PostLogoutRedirectUri" href="@Model.PostLogoutRedirectUri">here</a> to return to the
            <span>@Model.client</span> application.
        </div>
    }
    @if (Model.SignOutIframeUrl != null)
    {
        <iframe width="0" height="0" class="signout hidden" src="@Model.SignOutIframeUrl"></iframe>
    }
</div>
</div>
@section scripts
{
@if (Model.AutomaticRedirectAfterSignOut)
{
    <script src="~/js/signout-redirect.js"></script>
}
}

这是~jssignout-redirect.js里的javascript代码:

window.addEventListener("load", function () {
    var a = document.querySelector("a.PostLogoutRedirectUri");
    if (a) {
        window.location = a.href;
    }
});

$('#returnHomeBtn').on('click', function (e) {
    $('.PostLogoutRedirectUri').click();
});
© www.soinside.com 2019 - 2024. All rights reserved.