【问题标题】:How to logout ClaimsIdentity User when using OpenIDConnect使用 OpenIDConnect 时如何注销 ClaimsIdentity 用户
【发布时间】:2020-11-12 09:01:12
【问题描述】:

我有使用 OpenIDConnect 身份验证的 ASP.NET Core 应用程序。在OnTokenValidated 事件中,我检查Authenticated 用户是否存在于我的应用程序的数据库中,如果不存在,那么我将抛出UnauthorizedAccessException

请注意,在OnTokenValidated 事件中,如果用户存在于应用程序的数据库中,我将创建新身份,否则我将保留经过身份验证的用户。

public class Startup
{
    private readonly ILogger<Startup> _logger;
    public IConfiguration Configuration { get; }
    private IHostingEnvironment _environment { get; }

    public Startup(IConfiguration configuration, IHostingEnvironment env, ILogger<Startup> logger)
    {
        Configuration = configuration;
        _environment = env;
        _logger = logger;
    }

    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
         .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
         {
             options.LoginPath = "/Home";
             options.AccessDeniedPath = "/Account/Forbidden";
             options.Cookie = new CookieBuilder()
             {
                 Name = "myCookie",
                 HttpOnly = true,
             };
             options.SlidingExpiration = true;
         })
         .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
         {
             //options.ForwardChallenge                
             options.Authority = configuration["IdentityOptions:Authority"];
             options.ClientId = configuration["IdentityOptions:ClientID"];
             options.ResponseType = "id_token";
             options.CallbackPath = "/Home";
             options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; //tells cookies scheme to persist user's identity in cookie.
             options.SignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;//tells cookies scheme to remove persisted cookie
             options.Scope.Add(OpenIdConnectScope.Email);
             
             options.Events = new OpenIdConnectEvents()
             {
                 OnTokenValidated = async context =>
                 {
                     var emailClaim = context.Principal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Email);
                     
                    // check if user exists in client applications db
                     CompanyUser cu = null;
                     using (var serviceProvider = services.BuildServiceProvider())
                     {
                         using (var serviceScope = serviceProvider.CreateScope())
                         {
                             using (var accountService = serviceScope.ServiceProvider.GetService<IAccountService>())
                             {
                                 cu = await accountService.Authorize(emailClaim.Value);
                             }
                         }
                     }

                     if (cu == null)
                     {
                         // context.Principal.Identity.IsAuthenticated is true here
                         
                         throw new UnauthorizedAccessException(string.Format("Could not find user for login '{0}' ", emailClaim.Value));
                     }

                     //We will create new identity to store only required claims.
                     var newIdentity = new ClaimsIdentity(context.Principal.Identity.AuthenticationType);

                     // keep the id_token for logout 
                     newIdentity.AddClaim(new Claim(IdentityClaimTypes.IdToken, context.ProtocolMessage.IdToken));

                     // add email claim
                     newIdentity.AddClaim(emailClaim);                         
                     
                     context.Properties.IsPersistent = true;
                     context.Properties.ExpiresUtc = DateTime.UtcNow.AddHours(3);

                     // overwrite existing authentication ticket
                     context.Principal = new ClaimsPrincipal(newIdentity);
                 },                     
                 OnRedirectToIdentityProviderForSignOut = async context =>
                 {
                     var idTokenHint = context.HttpContext?.User?.FindFirst("id_token");
                     if (idTokenHint != null)
                         context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                     await Task.FromResult(0);
                 },
                 OnRemoteFailure = async context =>
                 {  
                     //WHY context.HttpContext.User.Identity.IsAuthenticated is false here??
                     if (context.Failure is UnauthorizedAccessException)
                     {
                         context.Response.Redirect("/Account/AccessDenied");
                     }
                     else
                     {
                         context.Response.Redirect("/Account/Error");
                     }
                     context.HandleResponse();
                     await Task.FromResult(0);
                 }
             };
         });
    }
    

}

问题

1> 在OnRemoteFailure 中,我如何在重定向到 AccessDenied 视图之前自动注销/清除unauthorized 用户? AccessDenied 视图可供匿名用户访问。 我试过了

OnRemoteFailure = async context =>
                     {
                         if (context.Failure is UnauthorizedAccessException)
                         {
                             await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.SignOutAsync(context.HttpContext, CookieAuthenticationDefaults.AuthenticationScheme);
                             await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.SignOutAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme);

                             context.Response.Redirect("/Account/AccessDenied");
                         }
                         else 
                         {
                             context.Response.Redirect("/Account/Error");
                         }
                         context.HandleResponse();
                         await Task.FromResult(0);
                     }

但是在OnTokenValidated 事件中context.HttpContext.User.Identity.IsAuthenticated 是假的,所以它不起作用。

【问题讨论】:

    标签: asp.net-core-mvc asp.net-core-2.0 identityserver4 openid-connect identityserver3


    【解决方案1】:

    一个常见的错误是在调用 Signout 方法后执行 Reponse.Redirect,这听起来很明显。但不幸的是,这不起作用。 Signout 方法会创建自己的重定向响应,当您进行重定向时,您会“覆盖”这些重定向

    实现这一点的正确方法是不返回任何内容,让 Signout 方法处理重定向:

        public async Task DoLogout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
        }
    

    要重定向到备用网址,您可以尝试:

            await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties( )
            {
                RedirectUri = "alternativeUrl"
            });
    

    【讨论】:

    • 我试过了,但这会将用户重定向到身份服务器的注销页面。我希望用户转到 AccessDenied 页面。我试过public async Task&lt;IActionresult&gt; AccessDenied() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); return View("AccessDenied"); }没用
    • 它确实被重定向了。可能是我在这里问了错误的问题。我已经开始了一个新线程stackoverflow.com/questions/63042878/…
    • 澄清你的问题总是好的。但是,为什么不在这两个 Signout 方法上设置 AuthenticationProperties { redirectUri} 呢?或者尝试改变它们的顺序?
    • 如果你不能让它工作,你总是可以作为一个黑客,只需清除本地会话 cookie 以在本地注销用户。
    • 您对我的回答满意还是缺少什​​么?如果没有,请将我的回答标记为已接受。
    猜你喜欢
    • 2016-11-07
    • 1970-01-01
    • 2023-03-31
    • 2021-06-22
    • 1970-01-01
    • 2012-01-24
    • 2023-04-10
    • 2014-07-17
    • 2021-09-08
    相关资源
    最近更新 更多