c# - "Identity.External"的 ASP.NET Core 身份异常

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

我在使用 ASP.NET Core 2.1 网站时遇到了一个奇怪的问题。当我登录并在 30 分钟后刷新时,我总是会抛出以下异常:

InvalidOperationException: No sign-out authentication handler is registered for the scheme 'Identity.External'. The registered sign-out schemes are: Identity.Application. Did you forget to call AddAuthentication().AddCookies("Identity.External",...)?

我没有注册 Identity.External 是正确的,但我也不希望它注册。为什么它一直试图注销它?以下是我注册 cookie 的方式:

services.AddAuthentication(
    o => {
        o.DefaultScheme = IdentityConstants.ApplicationScheme;
    }).AddCookie(IdentityConstants.ApplicationScheme,
    o => {
        o.Events = new CookieAuthenticationEvents {
            OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
        };
    });

services.ConfigureApplicationCookie(
    o => {
        o.Cookie.Expiration = TimeSpan.FromHours(2);
        o.Cookie.HttpOnly = true;
        o.Cookie.SameSite = SameSiteMode.Strict;
        o.Cookie.SecurePolicy = CookieSecurePolicy.Always;

        o.AccessDeniedPath = "/admin";
        o.LoginPath = "/admin";
        o.LogoutPath = "/admin/sign-out";
        o.SlidingExpiration = true;
    });

有人可以为我指出正确的方向来解决这个问题吗?

更新

这是@Edward在评论中要求的完整代码和使用过程。为简洁起见,我省略了一些部分。

Startup.cs

public sealed class Startup {
    public void ConfigureServices(
        IServiceCollection services) {
        //  ...
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddApplicationIdentity();
        services.AddScoped<ApplicationSignInManager>();

        services.Configure<IdentityOptions>(
            o => {
                o.Password.RequiredLength = 8;

                o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                o.Lockout.MaxFailedAccessAttempts = 5;
            });
        services.ConfigureApplicationCookie(
            o => {
                o.Cookie.Name = IdentityConstants.ApplicationScheme;
                o.Cookie.Expiration = TimeSpan.FromHours(2);
                o.Cookie.HttpOnly = true;
                o.Cookie.SameSite = SameSiteMode.Strict;
                o.Cookie.SecurePolicy = CookieSecurePolicy.Always;

                o.AccessDeniedPath = "/admin";
                o.LoginPath = "/admin";
                o.LogoutPath = "/admin/sign-out";
                o.SlidingExpiration = true;
            });
        //  ...
    }

    public void Configure(
        IApplicationBuilder app) {
        //  ...
        app.UseAuthentication();
        //  ...
    }
}

ServiceCollectionExtensions.cs

public static class ServiceCollectionExtensions {
    public static IdentityBuilder AddApplicationIdentity(
        this IServiceCollection services) {
        services.AddAuthentication(
            o => {
                o.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
                o.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
                o.DefaultForbidScheme = IdentityConstants.ApplicationScheme;
                o.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
                o.DefaultSignOutScheme = IdentityConstants.ApplicationScheme;
            }).AddCookie(IdentityConstants.ApplicationScheme,
            o => {
                o.Events = new CookieAuthenticationEvents {
                    OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
                };
            });

        services.TryAddScoped<SignInManager<User>, ApplicationSignInManager>();
        services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
        services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
        services.TryAddScoped<IdentityErrorDescriber>();
        services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<User>>();
        services.TryAddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsPrincipalFactory<User>>();
        services.TryAddScoped<UserManager<User>>();
        services.TryAddScoped<IUserStore<User>, ApplicationUserStore>();

        return new IdentityBuilder(typeof(User), services);
    }
}

DefaultController.cs

[Area("Admin")]
public sealed class DefaultController :
    AdminControllerBase {
    [HttpPost, AllowAnonymous]
    public async Task<IActionResult> SignIn(
        SignIn.Command command) {
        var result = await Mediator.Send(command);

        if (result.Succeeded) {
            return RedirectToAction("Dashboard", new {
                area = "Admin"
            });
        }

        return RedirectToAction("SignIn", new {
            area = "Admin"
        });
    }

    [HttpGet, ActionName("sign-out")]
    public async Task<IActionResult> SignOut() {
        await Mediator.Send(new SignOut.Command());

        return RedirectToAction("SignIn", new {
            area = "Admin"
        });
    }
}

登录.cs

public sealed class SignIn {
    public sealed class Command :
        IRequest<SignInResult> {
        public string Password { get; set; }
        public string Username { get; set; }
    }

    public sealed class CommandHandler :
        HandlerBase<Command, SignInResult> {
        private ApplicationSignInManager SignInManager { get; }

        public CommandHandler(
            DbContext context,
            ApplicationSignInManager signInManager)
            : base(context) {
            SignInManager = signInManager;
        }

        protected override SignInResult Handle(
            Command command) {
            var result = SignInManager.PasswordSignInAsync(command.Username, command.Password, true, false).Result;

            return result;
        }
    }
}

SignOut.cs

public sealed class SignOut {
    public sealed class Command :
        IRequest {
    }

    public sealed class CommandHandler :
        HandlerBase<Command> {
        private ApplicationSignInManager SignInManager { get; }

        public CommandHandler(
            DbContext context,
            ApplicationSignInManager signInManager)
            : base(context) {
            SignInManager = signInManager;
        }

        protected override async void Handle(
            Command command) {
            await SignInManager.SignOutAsync();
        }
    }
}

这里有所有相关代码,从我如何配置身份到我如何登录和退出。我仍然不知道为什么 Identity.External 在我从未要求的情况下出现。

从技术上讲,可以删除 SignInSignOut 类,并将它们的功能合并到 DefaultController 中,但是我选择保留它们以保留应用结构一致。

最佳答案

首先,我会避免扩展 ServiceCollection 类。相反,我会调用 AddIdetityCore 方法。查看源码here .

然后:

services.AddIdentityCore<ApplicationUser>()
                .AddUserStore<UserStore>()
                .AddDefaultTokenProviders()
                .AddSignInManager<SignInManager<ApplicationUser>>();

其次,您在 AddCookie 方法选项中设置 Events 属性。由于您没有为 ValidationInterval 属性设置时间段,因此它将持续整整 30 分钟。这意味着一旦时间结束,将在服务器执行的下一个请求中验证用户的 SecurityStamp 属性。由于在你的描述中你没有说你是否更改了密码,我怀疑用户的 SecurityStamp 在 BD 中为 null 而它的 Cookie 版本是一个空字符串,所以当 Identity 在两个版本之间进行验证时(null = = "") 它将是错误的并且 then Identity would try to close the session of the Application Scheme, the Extern one and also the TwoFactor .然后它会抛出异常,因为只有 ApplicationScheme 被注册了:

public virtual async Task SignOutAsync()
{
    await Context.SignOutAsync(IdentityConstants.ApplicationScheme);
    await Context.SignOutAsync(IdentityConstants.ExternalScheme); //<- Problem and...
    await Context.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme); //... another problem.
}

解决方案首先是确保 SecurityStamp 不为空。然后你有两个选择:

Adding the cookies for every scheme

或者

从 SignInManager 类覆盖 SignOutAsync 方法。

public class SignInManager<TUser> : Microsoft.AspNetCore.Identity.SignInManager<TUser> 
    where TUser : class
{
    public SignInManager(
        UserManager<TUser> userManager, 
        IHttpContextAccessor contextAccessor, 
        IUserClaimsPrincipalFactory<TUser> claimsFactory, 
        IOptions<IdentityOptions> optionsAccessor, 
        ILogger<Microsoft.AspNetCore.Identity.SignInManager<TUser>> logger, 
        IAuthenticationSchemeProvider schemes) 
        : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
    {
    }

    public async override Task SignOutAsync()
    {
        await Context.SignOutAsync(IdentityConstants.ApplicationScheme); // <- 
    }
}

然后:

services.AddIdentityCore<ApplicationUser>()
                .AddUserStore<UserStore>()
                .AddDefaultTokenProviders()
                .AddSignInManager<Services.Infrastructure.Identity.SignInManager<ApplicationUser>>() //<-

关于c# - "Identity.External"的 ASP.NET Core 身份异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51226707/

相关文章:

asp.net-core - IIS 无法运行 ASP.NET Core 站点 - HTTP 错误 502.5

ASP.NET 身份 : Authorize attribute with roles doesn't work on Azure

c# - ASP.NET 身份声明

c# - 我要如何在C#中统一创建音频可视化器

c# - ASP C# : Special characters: #, $,+ 无法通过 URL 参数

c# - 尝试使用 RSA 解密时 key 不存在

c# - ExceptionHandling : If controller method returns json then return json, if View then return Redirect

C# Linq groupby 方法语法

tfs - 如何以特定编号启动 vNext TFS 2015 构建修订版

c# - 使用 asp.net 身份注入(inject)