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

w00*_*977 7 c# asp.net-core identityserver4 asp.net-core-identity

我试图让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";
})
Run Code Online (Sandbox Code Playgroud)

2)配置IdentityServer应用程序以侦听端口5000并在身份服务器上禁用SSL.

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

public async Task Logout() 
{ 
    await HttpContext.SignOutAsync("Cookies"); 
    await HttpContext.SignOutAsync("oidc"); 
} 
Run Code Online (Sandbox Code Playgroud)

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

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

Kir*_*kin 4

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

public void OnGet() { }
Run Code Online (Sandbox Code Playgroud)

因为这是使用 ASP.NET Core Razor Pages,所以它所做的只是呈现相应的Logout.cshtml页面。在您的示例中,当您点击LogoutMVC 应用程序时,它会清除自己的 cookie,然后将您传递到 IS4 应用程序(OnGet特别是 )。因为这个OnGet处理程序是空的,所以它实际上并没有做任何事情,而且它肯定不会让您退出 IS4 应用程序。

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

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

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

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

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

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

    return View(vm);
}
Run Code Online (Sandbox Code Playgroud)

注销时,有一个设置可以控制是否首先提示用户确认是否要注销。这主要是这段代码所关心的——POST如果不需要提示,它会将其直接传递给请求处理程序。这是代码片段POST

[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);
}
Run Code Online (Sandbox Code Playgroud)

这里重要的一行是调用HttpContext.SignOutAsync- 这最终会删除 IS4 用于保持您登录状态的 cookie。一旦删除了该 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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这不是一个坏主意——这几乎是你唯一的选择。ASP.NET Core Identity 是支架式的,这意味着它可以帮助您入门,您可以根据需要进行自定义。您无法同时进行自定义和无缝升级。从好的方面来看,注销在不久的将来不太可能发生任何重大变化。 (2认同)