我在使用 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
在我从未要求的情况下出现。
从技术上讲,可以删除 SignIn
和 SignOut
类,并将它们的功能合并到 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/