如何解决将 .NET Core Identity 与 API 结合使用
我已经创建了一个 API 并从同一个 API 设置了 JWT 身份验证(我选择不使用 IdentityServer4)。
我是通过 services.AddAuthentication
然后我在控制器中创建了令牌并且它可以工作。
但是我现在想添加注册等。但我不想编写自己的代码来散列密码、处理注册电子邮件等。
所以我遇到了 ASP.NET Core Identity,它似乎是我需要的,除了它添加了一些我不需要的 UI 内容(因为它只是一个 API,而我想要完全独立的 UI)。
但是MSDN上写的是:
ASP.NET Core Identity 添加用户界面 (UI) 登录功能到 ASP.NET Core Web 应用程序。要保护 Web API 和 SPA,请使用以下其中一种 以下:
Azure 活动目录
Azure Active Directory B2C (Azure AD B2C)
IdentityServer4
那么仅将 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
可用于在生成令牌之前检查有效的登录名/凭据。
让我摆脱我的想法,捆绑 Identity 与 UI、cookies 以及添加这个或那个但不添加这个或那个的各种令人困惑的扩展方法是相当烦人的,至少当你构建不需要 Cookie 或 UI 的现代 Web API。
在某些项目中,我还使用 Identity 手动生成 JWT 令牌以实现会员功能和用户/密码管理。
基本上最简单的方法就是检查源代码。
-
AddDefaultIdentity()
添加身份验证、添加身份 cookie、添加 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>,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()
,请确保在调用 AddAuthentication()
之后运行您的 AddIdentity()
配置,因为您必须覆盖默认的身份验证方案(我遇到了与此相关的问题,但不记得详细信息)。
(阅读本文的人可能会感兴趣的另一条信息是 SignInManager.PasswordSignInAsync()
、SignInManager.CheckPasswordSignInAsync()
和 UserManager.CheckPasswordAsync()
之间的区别。这些都是您可以找到并调用授权的公共方法目的。PasswordSignInAsync()
实现双因素登录(也设置 cookie;可能仅在使用 AddIdentity()
或 AddDefaultIdentity()
时)并调用 CheckPasswordSignInAsync()
,实现用户锁定处理并调用 {{ 1}},它只是检查密码。因此,为了获得正确的身份验证,最好不要直接调用 UserManager.CheckPasswordAsync()
,而是通过 UserManager.CheckPasswordAsync()
进行调用。但是,在单因素 JWT 令牌场景中,可能不需要调用 CheckPasswordSignInAsync()
(并且您可能会遇到重定向问题)。如果您在 Startup 中包含了 PasswordSignInAsync()
并设置了正确的 JwtBearer 令牌方案,那么下次客户端发送请求时附加有效令牌,身份验证中间件将启动,客户端将“登录”;即任何有效的 JWT 令牌都将允许客户端访问受 [Authorize] 保护的控制器操作。)
而且 IdentityServer 与 Identity 完全分开。事实上,IdentityServer 的体面实现是将其用作独立的文字身份服务器,为您的服务发布令牌。但是由于 ASP.NET Core 没有内置令牌生成功能,很多人最终在他们的应用程序中运行这个臃肿的服务器只是为了能够使用 JWT 令牌,即使他们只有一个应用程序并且没有真正的用途为中央机关。我并不是要讨厌它,它是一个非常棒的解决方案,具有很多功能,但如果能为更常见的用例提供更简单的东西会很好。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。