[英]Use .NET Core Identity with an API
我创建了一个 API 并从同一个 API 设置 JWT 身份验证(我选择不使用 IdentityServer4)。
我通过services.AddAuthentication
做到了这一点
然后我在 controller 中创建了令牌,它可以工作。
但是我现在想添加注册等。但我不想编写自己的代码来散列密码、处理注册电子邮件等。
所以我遇到了 ASP.NET 核心标识,它似乎是我需要的,除了它添加了一些我不需要的 UI 内容(因为它只是一个 API 和我想要完全独立的 UI)。
但是在MSDN上是这样写的:
ASP.NET Core Identity 向 ASP.NET Core web 应用程序添加了用户界面 (UI) 登录功能。 要保护 web API 和 SPA,请使用以下方法之一:
Azure 活动目录
Azure Active Directory B2C (Azure AD B2C)
身份服务器4
那么,仅将 Core Identity 用于 API 的散列和注册逻辑真的是个坏主意吗? 我不能忽略 UI 功能吗? 这很令人困惑,因为我宁愿不使用 IdentityServer4 或创建自己的用户管理逻辑。
您只需将 Identity 配置为使用 JWT 不记名令牌。 就我而言,我使用的是加密令牌,因此根据您的用例,您可能需要调整配置:
// In Startup.ConfigureServices...
services.AddDefaultIdentity<ApplicationUser>(
options =>
{
// Configure password options etc.
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configure authentication
services.AddAuthentication(
opt =>
{
opt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
TokenDecryptionKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my key")),
RequireSignedTokens = false, // False because I'm encrypting the token instead
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
// Down in Startup.Configure add authn+authz middlewares
app.UseAuthentication();
app.UseAuthorization();
然后在用户想要登录时生成一个令牌:
var encKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my key"));
var encCreds = new EncryptingCredentials(encKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512);
var claimsIdentity = await _claimsIdentiyFactory.CreateAsync(user);
var desc = new SecurityTokenDescriptor
{
Subject = claimsIdentity,
Expires = DateTime.UtcNow.AddMinutes(_configuration.Identity.JwtExpirationMinutes),
Issuer = _configuration.Identity.JwtIssuer,
Audience = _configuration.Identity.JwtAudience,
EncryptingCredentials = encCreds
};
var token = new JwtSecurityTokenHandler().CreateEncodedJwt(desc);
// Return it to the user
然后,您可以使用UserManager
来处理创建新用户和检索用户,而SignInManager
可用于在生成令牌之前检查有效的登录/凭据。
让我谈谈捆绑身份与 UI 的关系,cookies 以及添加这个或那个但不添加这个或那个的各种令人困惑的扩展方法,这很烦人,至少在你构建现代 web 时不需要 cookies 和 UI 的 API。
在某些项目中,我还使用带有身份的手动 JWT 令牌生成来进行会员功能和用户/密码管理。
基本上最简单的事情就是检查源代码。
AddDefaultIdentity()
添加身份验证,添加身份 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 identity depends on
services.AddOptions().AddLogging();
// Services used by identity
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>>();
// No interface for the error describer so we can add errors without rev'ing the interface
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 used by identity
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);
});
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Identity services
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>>();
// No interface for the error describer so we can add errors without rev'ing the interface
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()
实现双因素登录(也设置 cookies;可能仅在使用AddIdentity()
或AddDefaultIdentity()
时)并调用CheckPasswordSignInAsync()
,它实现用户锁定处理并调用UserManager.CheckPasswordAsync()
,它只检查密码。所以要获得正确的身份验证,最好不要直接调用UserManager.CheckPasswordAsync()
,而是通过CheckPasswordSignInAsync()
进行。但是,在单因素 JWT 令牌场景中,可能不需要调用PasswordSignInAsync()
(您可以运行进入重定向问题)。如果您已包含UseAuthentication()/AddAuthentication()
UseAuthentication()/AddAuthentication()
在 Startup 中设置正确的 JwtBearer 令牌方案,然后客户端下次发送带有有效令牌的请求时,身份验证中间件将启动,并且客户端将“登录”; 即任何有效的 JWT 令牌将允许客户端访问受 [Authorize] 保护的 controller 操作。)
幸运的是,IdentityServer 与 Identity 完全分开。 事实上,IdentityServer 的体面实现是将其用作独立的文字身份服务器,为您的服务颁发令牌。 但是由于 ASP.NET 内核没有内置令牌生成功能,很多人最终在他们的应用程序中运行这个臃肿的服务器只是为了能够使用 JWT 令牌,即使他们只有一个应用程序并且他们没有真正使用中央。 我并不是要讨厌它,它是一个非常棒的解决方案,具有很多功能,但是对于更常见的用例来说,有一些更简单的东西会很好。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.