将 .NET Core Identity 与 API 一起使用

Use .NET Core Identity with an API

我创建了一个 API 并从同一个 API 设置了 JWT 身份验证(我选择不使用 IdentityServer4)。

我通过 services.AddAuthentication

做到了这一点

然后我在控制器中创建了令牌并且它起作用了。

但是我现在想添加注册等。但我不想编写自己的代码来散列密码、处理注册电子邮件等。

所以我遇到了 ASP.NET Core Identity,它似乎是我需要的,除了它添加了一些我不需要的 UI 东西(因为它只是一个 API还有 UI 我想要完全独立)。

但是MSDN上写着:

ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following:

Azure Active Directory

Azure Active Directory B2C (Azure AD B2C)

IdentityServer4

那么,仅将 Core Identity 用于 API 的散列和注册逻辑真的是个坏主意吗?我不能忽略 UI 功能吗?这非常令人困惑,因为我宁愿不使用 IdentityServer4 或创建我自己的用户管理逻辑。

您只需将身份配置为使用 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 可用于在生成令牌之前检查有效 login/credentials。

让我直言不讳,捆绑身份与 UI、cookie 和令人困惑的各种扩展方法一起添加这个或那个,但不要添加这个或那个,这很漂亮烦人,至少当你构建不需要 cookie 或 UI.

的现代 Web API 时是这样

在一些项目中,我还使用带有身份的手动 JWT 令牌生成来实现成员功能和 user/password 管理。

基本上最简单的事情就是检查源代码。

  1. 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();
}
  1. AddIdentityCore()是一个更精简的版本,只添加了基本服务,但它甚至没有添加身份验证,也不支持角色(这里你已经可以看到添加了哪些单独的服务,以change/override/remove 如果你愿意,他们):

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);
}

到目前为止,这种说法是有道理的,对吧?

  1. 但是输入AddIdentity(),看起来是最臃肿的,唯一直接支持roles的,但是比较迷惑的是好像没有加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(),请务必 运行 您的 AddAuthentication() 配置 调用 AddIdentity() 之后,因为你必须覆盖默认的身份验证方案(我 运行 进入与此相关的问题,但不记得细节)。

(阅读本文的人可能会感兴趣的另一个信息是 SignInManager.PasswordSignInAsync()SignInManager.CheckPasswordSignInAsync()UserManager.CheckPasswordAsync() 之间的区别。这些都是 public 方法可以找到并调用授权目的。PasswordSignInAsync() 实现双因素登录(也设置 cookie;可能仅在使用 AddIdentity()AddDefaultIdentity() 时)并调用 CheckPasswordSignInAsync(),它实现用户锁定处理和调用 UserManager.CheckPasswordAsync(),它只是检查密码。所以要获得正确的身份验证,最好不要直接调用 UserManager.CheckPasswordAsync(),而是通过 CheckPasswordSignInAsync() 来完成。但是,在一个单一的-factor JWT 令牌场景,可能不需要调用 PasswordSignInAsync()(您可以 运行 解决重定向问题)。如果您在 Startup 中包含 UseAuthentication()/AddAuthentication() 并设置了正确的 JwtBearer 令牌方案,那么下一次客户端发送带有有效令牌的请求时,身份验证中间件将启动,客户端将是 'signed in';即任何有效的 JWT 令牌都将允许客户端访问受 [Authorize] 保护的控制器操作.)

幸运的是,IdentityServer 与 Identity 完全分开。事实上,IdentityServer 的体面实现是将其用作独立的文字身份服务器,为您的服务颁发令牌。但是由于 ASP.NET Core 没有内置的令牌生成功能,很多人最终 运行 在他们的应用程序中安装这个臃肿的服务器只是为了能够使用 JWT 令牌,即使他们有一个应用程序,它们对中央机构没有真正的用处。我并不是有意讨厌它,它是一个非常棒的解决方案,具有很多功能,但是对于更常见的用例来说,如果能有更简单的东西就好了。