让我先说一下,Identity与UI、Cookies及令人困惑的各种扩展方法捆绑在一起,有些烦人,至少在构建不需要Cookies或UI的现代Web API时如此。
在某些项目中,我还使用Identity手动生成JWT令牌来进行成员资格功能和用户/密码管理。
基本上,最简单的方法是查看源代码。
1.
AddDefaultIdentity()
添加身份验证、添加Identity cookies、添加UI,并调用
AddIdentityCore()
;但不支持角色:
public static IdentityBuilder AddDefaultIdentity<TUser>(this IServiceCollection services, Action<IdentityOptions> configureOptions) where TUser : class
{
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });
return services.AddIdentityCore<TUser>(o =>
{
o.Stores.MaxLengthForKeys = 128;
configureOptions?.Invoke(o);
})
.AddDefaultUI()
.AddDefaultTokenProviders();
}
AddIdentityCore()
是一个更加简化版本的方法,它仅添加基本服务,但不包括身份验证和角色支持(在这里可以看到已添加的个别服务,如果需要可以更改/覆盖/删除它们):
public static IdentityBuilder AddIdentityCore<TUser>(this IServiceCollection services, Action<IdentityOptions> setupAction)
where TUser : class
{
services.AddOptions().AddLogging();
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
services.TryAddScoped<UserManager<TUser>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), services);
}
目前为止,这种解释还算讲得通,对吧?
- 但是现在加入
AddIdentity()
之后,它似乎是最臃肿的一个,唯一直接支持角色的方法,但令人困惑的是它似乎没有添加UI界面:
public static IdentityBuilder AddIdentity<TUser, TRole>(
this IServiceCollection services,
Action<IdentityOptions> setupAction)
where TUser : class
where TRole : class
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
services.AddHttpContextAccessor();
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
总的来说,你可能需要使用
AddIdentityCore()
,并自己使用
AddAuthentication()
。此外,如果您使用了
AddIdentity()
,请确保在调用
AddIdentity()
之后运行
AddAuthentication()
配置,因为您必须覆盖默认的身份验证方案(我遇到了与此相关的问题,但是无法记住细节)。还有一些关于授权的信息可能对阅读此内容的人很有趣,比如
SignInManager.PasswordSignInAsync()
、
SignInManager.CheckPasswordSignInAsync()
和
UserManager.CheckPasswordAsync()
之间的区别。这些都是公共方法,您可以找到并调用它们进行授权。
PasswordSignInAsync()
实现了双因素登录(还设置 cookie;可能仅在使用
AddIdentity()
或
AddDefaultIdentity()
时才使用)并调用
CheckPasswordSignInAsync()
,该方法实现了用户锁定处理并调用
UserManager.CheckPasswordAsync()
,该方法仅检查密码。因此,为了获得适当的身份验证,最好不要直接调用
UserManager.CheckPasswordAsync()
,而是通过
CheckPasswordSignInAsync()
进行调用。但是,在单因素 JWT 令牌方案中,调用
PasswordSignInAsync()
可能是不需要的(并且可能会遇到重定向问题)。如果你在Startup中包含了
UseAuthentication()/AddAuthentication()
并设置了适当的 JwtBearer 令牌方案,则客户端下一次附带有效令牌发送请求时,身份验证中间件将启动,客户端将被“登录”;即任何有效的 JWT 令牌都将允许客户端访问受 [Authorize] 保护的控制器操作。值得庆幸的是,IdentityServer 完全与 Identity 分离。实际上,IdentityServer 的不错实现是将其用作独立的文字身份服务器,为您的服务发放令牌。但是,由于 ASP.NET Core 没有内置的令牌生成功能,因此很多人最终在其应用程序中运行这个臃肿的服务器,只是为了能够使用 JWT 令牌,尽管他们只有一个应用程序并且没有实际用途的中央权威机构。我的意思不是贬低它,它是一个拥有很多特性的真正伟大的解决方案,但对于更常见的用例,拥有一些更简单的东西会很好。
UserManager
、SignInManager
等),你可以使用它们。 - XerillioSignInManager
是否仍然有效?它不是期望cookie吗?还是它会适应JWT。 - Jonathan Daniel