.Net Core 2.0 Web API using JWT - 添加身份会破坏 JWT 身份验证

Posted

技术标签:

【中文标题】.Net Core 2.0 Web API using JWT - 添加身份会破坏 JWT 身份验证【英文标题】:.Net Core 2.0 Web API using JWT - Adding Identity breaks the JWT authentication 【发布时间】:2018-03-01 14:53:32 【问题描述】:

(编辑 - 找到正确的修复方法!见下文)

好的 - 这是我第一次尝试 .Net Core 2.0 和身份验证,尽管我过去曾使用 Web API 2.0 做过一些事情,并且在过去几年中在各种 MVC 和 Webforms ASP 项目上进行了相当广泛的工作.

我正在尝试使用 .Net Core 创建一个仅限 Web API 的项目。这将形成用于生成一些报告的多租户应用程序的后端,因此我需要能够对用户进行身份验证。似乎通常的方法是使用 JWT - 首先对用户进行身份验证以生成令牌,然后将其传递给客户端以在每个 API 请求上使用。将使用 EF Core 存储和检索数据。

我遵循this post 的基本方法来进行此设置,并且我设法让它正常工作 - 我有一个控制器,它接受用户名/密码并在有效时返回令牌,并设置了一些授权策略根据声明。

接下来我需要实际管理用户/密码/等。我以为我会为此使用.Net Core Identity,因为这样我就有很多现成的代码来担心用户/角色、密码等。我使用的是自定义的User 类和UserRole 派生的类来自标准的 IdentityUserIdentityRole 类,但我现在已经恢复到标准类。

我遇到的问题是,我不太清楚如何添加身份和注册所有各种服务(rolemanager、usermanager 等)而不破坏身份验证 - 基本上只要我将此行添加到我的 @ 987654327@班级:

services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<MyContext>();

一切都出错了,当我收到请求时,我再也看不到任何索赔,所以所有政策都被锁定了,你什么也得不到。

如果我没有这些行,那么我最终会遇到与 UserManager、RoleManager、UserStore 等相关的错误。所有这些都没有为 DI 注册。

那么......我如何(如果可能的话)注册身份并将其正确连接到上下文,但避免/删除对实际授权机制的任何更改?

我在网上查了很多资料,但是自从 .Net Core 1.x 以来,很多情况都发生了变化,所以很多教程等都不再有效了。

我不打算让这个 API 应用程序有任何前端代码,所以我现在不需要对表单或任何东西进行任何 cookie 身份验证。

编辑 好的,我现在发现在这段代码中,在 Startup.ConfigureServices() 方法中设置 JWT 身份验证:

 services.AddAuthentication(
            JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                
                 >>breakpoint>>>   options.TokenValidationParameters =
                        new TokenValidationParameters
                        
                            ValidateIssuer = true,
                            ValidateAudience = true,
                            ValidateLifetime = true,
                            ValidateIssuerSigningKey = true,

                            ValidIssuer = "Blah.Blah.Bearer",
                            ValidAudience = "Blah.Blah.Bearer",
                            IssuerSigningKey =
                            JwtSecurityKey.Create("verylongsecretkey")

                        ;
                );

如果我在指示的行放置一个断点(通过“>>breakpoint>>>”),那么当我添加行以添加身份服务时它会被命中,但如果我添加这些行然后它永远被击中。无论我在方法中的哪个位置调用services.AddIdentity(),这都是正确的。我知道这只是一个 lambda,所以它会在稍后执行,但是有什么方法可以让 AddIdentity 的东西不设置身份验证,或者让代码立即删除它?我假设在某些时候有一些代码选择不运行我在那里设置的配置的 Lambda,因为 Identity 的东西已经设置了它......

感谢您阅读所有内容,如果您有 :)

编辑 - 找到答案 好的,我最终发现了这个 GH 问题,基本上就是这个问题: https://github.com/aspnet/Identity/issues/1376

基本上我要做的是双重的:

确保首先调用services.AddIdentity&lt;IdentityUser, IdentityContext()

更改调用以添加身份验证来自:

services.AddAuthentication(
            JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
...

收件人:

services.AddAuthentication(options =>
        
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        )
            .AddJwtBearer(options =>
...

这确实会令人讨厌地导致创建一个 cookie,但据我所知,这不会用于身份验证 - 它纯粹是在对具有 [Authorize(Policy = "Administrator")] 或类似设置的控制器/操作的请求中使用不记名令牌最少。

我需要进行更多测试,如果我发现它在某些方面不起作用,我会尝试回到这里更新。

(已编辑 - 现在将正确的解决方案作为答案)

【问题讨论】:

你救了我。谢谢 你应该考虑把它分解成一个问题,然后自己作为一个正确的答案来回答。 @alwayslearning 不是一个坏主意...所以我这样做了。 【参考方案1】:

我最终将解决方案放在一起,因此根据用户 alwayslearning 的建议,我编辑了我的帖子,并将其作为实际答案。

好的,这可以正确完成。首先,您需要使用我在上面的编辑中指出的身份验证选项——这很好。 那么你需要使用services.AddIdentityCore&lt;TUser&gt;()而不是services.AddIdentity&lt;TUser&gt;()。然而,这并没有为角色管理添加一大堆东西,并且显然缺乏适当的构造函数来为其提供您想要使用的角色类型。这意味着就我而言,我必须这样做:

  IdentityBuilder builder = services.AddIdentityCore<IdentityUser>(opt =>
        
            opt.Password.RequireDigit = true;
            opt.Password.RequiredLength = 8;
            opt.Password.RequireNonAlphanumeric = false;
            opt.Password.RequireUppercase = true;
            opt.Password.RequireLowercase = true;
        
        );
        builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
        builder
            .AddEntityFrameworkStores<MyContext>();
        //.AddDefaultTokenProviders();

        builder.AddRoleValidator<RoleValidator<IdentityRole>>();
        builder.AddRoleManager<RoleManager<IdentityRole>>();
        builder.AddSignInManager<SignInManager<IdentityUser>>();

完成后,接下来要确保在验证用户登录时(在发送令牌之前),确保使用 SignInManager 方法 CheckPasswordSignInAsync @987654325 @:

public async Task<IdentityUser> GetUserForLogin(string userName, string password)
       
        //find user first...
        var user = await _userManager.FindByNameAsync(userName);

        if (user == null)
        
            return null;
        

        //validate password...
        var signInResult = await _signInManager.CheckPasswordSignInAsync(user, password, false);

        //if password was ok, return this user.
        if (signInResult.Succeeded)
        
            return user;
        

        return null;
    

如果您使用PasswordSignInAsync 方法,那么您将收到运行时错误。没有配置 IAuthenticationSignInHandler。

我希望这对某些人有所帮助。

【讨论】:

你绝对帮了我。非常感谢。当您没有 JWT 或 Identity 经验时,互联网上有许多过时的教程,很难找到解决方案。 我想问一下builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);是做什么的? @t123yh 这是解决方法的一部分,因为 AddIdentityCore() 方法没有一个构造函数,该构造函数采用正在使用的角色类型(在我的情况下,我只是使用内置的IdentityRole 类,而从它派生以向角色添加自定义属性是很常见的,在这种情况下,您需要将派生类传递给它)。基本上,它是启用后续行的解决方法,这些行将 RoleValidator、RoleManager 和 SignInManager 添加/注册到 DI 系统,以便所有中间件都可以使用它们。 很好的答案感谢您还提到了 checkpassworkasync 调用。不知道为什么这些信息似乎被埋没了,而且很难在 .net core 中找到 auth 的例子。 @GPW 尽管我遇到的所有帖子都使用了重新实例化方法,但它似乎无需重新实例化 IdentityBuilder 变量就可以正常工作。 var builder = services.AddIdentityCore(o => ... ) .AddRoles() .AddEntityFrameworkStores()【参考方案2】:

我从 github 中提取了AddIdentity 代码,并基于它创建了一个扩展方法,它不添加默认的 Cookie Authenticator,它现在与内置的 AddIdentityCore 非常相似,但可以接受 IdentityRole

 /// <summary>
 /// Contains extension methods to <see cref="IServiceCollection"/> for configuring identity services.
 /// </summary>
 public static class IdentityServiceExtensions
 
     /// <summary>
     /// Adds the default identity system configuration for the specified User and Role types. (Without Authentication Scheme)
     /// </summary>
     /// <typeparam name="TUser">The type representing a User in the system.</typeparam>
     /// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
     /// <param name="services">The services available in the application.</param>
     /// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
     public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services)
         where TUser : class
         where TRole : class
         => services.AddIdentityWithoutAuthenticator<TUser, TRole>(setupAction: null);

     /// <summary>
     /// Adds and configures the identity system for the specified User and Role types. (Without Authentication Scheme)
     /// </summary>
     /// <typeparam name="TUser">The type representing a User in the system.</typeparam>
     /// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
     /// <param name="services">The services available in the application.</param>
     /// <param name="setupAction">An action to configure the <see cref="IdentityOptions"/>.</param>
     /// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
     public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction)
         where TUser : class
         where TRole : class
     
         // 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<UserManager<TUser>>();
         services.TryAddScoped<SignInManager<TUser>>();
         services.TryAddScoped<RoleManager<TRole>>();

         if (setupAction != null)
         
             services.Configure(setupAction);
         

         return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
     
 

现在您可以像这样从 WebApi 项目中正常使用上述代码

.AddIdentityWithoutAuthenticator<User, IdentityRole>()

【讨论】:

+1 表示努力。我可能会使用它,但我现在所拥有的一切正常。对于让生活更轻松绝对有用,希望这个问题的任何新手现在都会有更整洁的代码:)

以上是关于.Net Core 2.0 Web API using JWT - 添加身份会破坏 JWT 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 2.0 中 Web API 的本地用户帐户存储

asp.net core 2.0 web api基于JWT自定义策略授权

.Net Core 2.0 Web API 使用 Identity / JWT 并让用户管理器使用 DI

用VSCode开发一个asp.net core2.0+angular5项目: Angular5+asp.net core 2.0 web api文件上传

.Net Core 2.0 Web API using JWT - 添加身份会破坏 JWT 身份验证

将 JWT Bearer Authentication Web API 与 Asp.Net Core 2.0 结合使用的问题