【问题标题】:Why does the end user have to log out twice?为什么最终用户必须注销两次?
【发布时间】:2019-01-16 17:14:35
【问题描述】:

我正在尝试让 IdentityServer4 在新的 .NET Core 2.1 应用程序中运行(它在 .NET Core 2.0 应用程序中完美运行)。我尝试了以下方法:

1)下载这个项目,也就是IdentityServer4应用:https://github.com/ghstahl/IdentityServer4-Asp.Net-2.1-Identity-Examples/tree/e0aeeff7e078aa082c8e16029dd2c220acc77d7b

2) 下载这个项目,它是使用 Identity Server4 应用程序的 MVC 应用程序:https://github.com/IdentityServer/IdentityServer4.Samples/tree/dev/Quickstarts/6_AspNetIdentity/src/MvcClient

3) 将两个项目添加到同一个解决方案中。 MVC 项目使用 IdentityServer 项目进行认证;授权等

我必须进行以下更改:

1) 更改 IdentityServer 应用程序中包含的 Startup(AddIdentityServer 现在接受一个参数):

services.AddIdentityServer(options =>
{
    options.UserInteraction.LoginUrl = "/Identity/Account/Login";
    options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
})

2) 将 IdentityServer 应用配置为侦听端口 5000 并在身份服务器上禁用 SSL。

除注销功能外,一切都按预期开箱即用。当我在 MVC 应用程序中单击注销时;在 MVC 应用程序中调用以下代码:

public async Task Logout() 
{ 
    await HttpContext.SignOutAsync("Cookies"); 
    await HttpContext.SignOutAsync("oidc"); 
} 

然后用户被重定向到 IdentityServer 应用程序中的 Logout.cshtml。但是,他们必须再次单击注销(在 IdentityServer 应用程序上)才能真正注销,即他们在 MVC 应用程序中单击注销(第二点),然后在 IdentityServer 中注销(第一点)。

为什么最终用户必须注销两次?

【问题讨论】:

  • 没用过,不过SignOutAsync那个does not require the schema parameter超载了。
  • @bradbury9,你能详细说明一下吗?该链接中的所有 SignOutAsync 构造函数都至少接受一个参数。谢谢。
  • 当然,它们是扩展方法,所以它的第一个参数是HttpContext 对象。我指的是this overload 可以叫System.Threading.Tasks.Task.SignOutAsync(HttpContext); o HttpContext.SignOutAsync(); 第一个直接调用静态方法,第二个直接使用扩展方法。
  • 在这种情况下,您登录了两次:一次在 MVC 应用程序中,一次在 IS4 应用程序本身中。当您在 MVC 应用程序中点击 Logout 时,您会注销 MVC 应用程序,但仍使用 IS4 登录。这有点像使用 Google 登录应用程序 - 您登录的是 Google 本身使用 Google 的应用程序。当您退出使用 Google 的上述应用时,您不会同时退出 Google。
  • 一般提示:打开浏览器的开发工具并查看网络选项卡。在那里你可以看到浏览器实际要去哪里,所以你可以很容易地看到它何时重定向回身份服务器。您还可以在此处查看 HTTP 响应标头,以便检查何时设置或清除 cookie。 – 这在调试身份验证流程相关问题时通常非常有用。

标签: c# asp.net-core identityserver4 asp.net-core-identity


【解决方案1】:

Account/Logout 页面中,位于脚手架的 ASP.NET Core 标识代码中的 Areas/Identity/Account/Logout.cshtml.cs 下,有一个如下所示的 OnGet 处理程序:

public void OnGet() { }

因为这是使用 ASP.NET Core Razor 页面,所以所做的只是渲染相应的 Logout.cshtml 页面。在您的示例中,当您在 MVC 应用程序中点击 Logout 时,它会清除自己的 cookie,然后将您传递给 IS4 应用程序(特别是 OnGet)。因为这个OnGet 处理程序是空的,所以它并没有真正做任何事情,当然也不会让你退出IS4 应用程序。

如果您查看Logout.cshtml.cs 内部的OnPost 处理程序,您会看到它看起来像这样:

public async Task<IActionResult> OnPost(string returnUrl = null)
{
    await _signInManager.SignOutAsync();
    // ...
}

SignOutAsync 的调用完全符合它的建议:它使您退出IS4 本身。但是,在您当前的工作流程中,不会调用此 OnPost 处理程序。正如我已经提到的,当您在 MVC 应用程序中使用 Logout 时,会间接调用 OnGet 处理程序。

现在,如果您查看 Quickstart.UI 项目中 IS4 注销的控制器/操作实现,您会发现它基本上将 GET 请求传递给 POST 请求。这是代码,去掉了 cmets:

[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
    var vm = await BuildLogoutViewModelAsync(logoutId);

    if (vm.ShowLogoutPrompt == false)
        return await Logout(vm);

    return View(vm);
}

退出时,有一个设置控制是否应首先提示用户确认是否要退出。这主要是这段代码所要处理的——如果不需要提示,它会直接将其传递给POST 请求处理程序。这是POST的代码的sn-p:

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

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

        // ...
    }

    // ...

    return View("LoggedOut", vm);
}

这里重要的一行是对HttpContext.SignOutAsync 的调用 - 这最终会删除 IS4 用来让您保持登录状态的 cookie。一旦删除,您就会退出 IS4。最终,这就是您当前的实施所缺少的。

在最简单的层面上,您可以通过将OnGet 更新为如下所示来解决您的问题:

public async Task<IActionResult> OnGet()
{
    if (User?.Identity.IsAuthenticated == true)
    {
        await _signInManager.SignOutAsync();          
        return RedirectToPage(); // A redirect ensures that the cookies has gone.
    }

    return Page();
}

这不支持我在上面详述的ShowLogoutPrompt 选项,只是为了让这个答案更短一点。除此之外,它只是使用_signInManager 进行注销,因为您处于 ASP.NET Core Identity 世界中。

我鼓励您探索 Quickstart.UI 实现的完整源代码,以支持 ShowLogoutPromptreturnUrl 等 - 如果不写一本书,我不可能在这里做到这一点。

【讨论】:

  • 谢谢。这确实有效+1。注销后可以重定向到 MVC 应用程序(而不是显示注销页面)。我不记得如何做到这一点 - 最近有太多的网络表单工作。 +1。
  • 是的,你可以这样做。您需要在 MVC 应用程序的 IS4 配置中设置 PostLogoutRedirectUri。一旦你知道了,看看this code,它显示了如何获取注销上下文,然后是PostLogoutRedirectUri 值。实际上,这里的其他答案似乎已经专门为您提供了代码。
  • 这样修改代码是不是一个坏主意?如果我以后想升级 ASP.NET Identity 怎么办?我想我必须再次进行所有修改?
  • 这不是一个坏主意 - 这几乎是您唯一的选择。 ASP.NET Core Identity 是脚手架的,这意味着它可以帮助您入门,您可以根据需要进行自定义。您不能同时进行自定义和无缝升级。从好的方面来说,注销不太可能在不久的将来发生任何重大变化。
  • @KirkLarkin 似乎您忘记提醒 TS 单点注销功能。在考虑直接跳转到 PostLogout Uri 时,至少应该牢记这一点。
【解决方案2】:

简单的注销功能可能如下:

        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;
        private readonly IIdentityServerInteractionService _interaction;
        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger,
            IIdentityServerInteractionService interaction)
        {
            _signInManager = signInManager;
            _logger = logger;
            _interaction = interaction;
        }

        public async Task<IActionResult> OnGet(string logoutId)
        {
            return await OnPost(logoutId);
        }

        public async Task<IActionResult> OnPost(string logoutId)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            var r = await _interaction.GetLogoutContextAsync(logoutId);
            if (r.PostLogoutRedirectUri == null)
            {
                return Redirect("/");
            }
            return Redirect(r.PostLogoutRedirectUri);
        }

【讨论】:

  • 跳转到PostLogoutRedirectUri 时的一个限制是单点注销。如果您根本不需要它,那就太好了。否则,您必须显示 LoggedOut 视图以在所有涉及的客户端呈现调用注销端点的 IFrame。我们的解决方案是首先显示LoggedOut 视图,然后使用JS 和计时器重定向到PostLogoutRedirectUri
【解决方案3】:

我遇到了同样的问题,我为自己解决了,并决定将我的答案放在这里供其他人查看。

解决方案: IdentityServer4 内部已经有快速启动项目逻辑,可以根据用户需求对其进行配置。

  1. 打开解决方案名称/Quickstart/Account/AccountOptions.cs
  2. ShowLogoutPrompt 设置为 false
  3. AutomaticRedirectAfterSignOut 设置为 true

我希望这会有所帮助,祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 2015-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多