使用JWT的.Net Core 2.0 Web API - 添加标识会破坏JWT身份验证

GPW*_*GPW 18 jwt asp.net-identity-2 asp.net-core-webapi

(编辑 - 找到正确的解决方案!见下文)

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

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

我按照这篇文章找到了这个设置的基本方法,我设法让它工作正常 - 我有一个接受用户名/密码的控制器并返回一个令牌(如果有效),并且一些授权策略设置基于索赔.

我需要的下一件事是实际管理用户/密码/等.我以为我只是使用.Net核心身份,因为我会有很多现成的代码来担心用户/角色,密码等.我使用的是自定义User类和UserRole派生自标准IdentityUserIdentityRole类的类,但我现在已经恢复到标准的那个.

我遇到的问题是我无法弄清楚如何添加身份并注册所有各种服务(rolemanager,usermanager等)而不会破坏身份验证 - 基本上只要我将此行添加到我的Startup.ConfigureServices类中:

services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<MyContext>();
Run Code Online (Sandbox Code Playgroud)

这一切都出错了,当我收到请求时,我再也看不到任何索赔,所以所有的政策都锁定了,你无法得到任何东西.

如果我没有那些行,那么我最终会遇到与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")

                        };
                });
Run Code Online (Sandbox Code Playgroud)

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

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

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

基本上我必须做的是双重的:

请确保调用services.AddIdentity<IdentityUser, IdentityContext()制成第一

更改呼叫以从以下位置添加身份验证:

services.AddAuthentication(
            JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
...
Run Code Online (Sandbox Code Playgroud)

至:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
            .AddJwtBearer(options =>
...
Run Code Online (Sandbox Code Playgroud)

这会令人烦恼地导致cookie被创建,但据我所知,这不会用于身份验证 - 它纯粹是[Authorize(Policy = "Administrator")]在对至少具有或类似设置的控制器/动作的请求上使用承载令牌.

我需要测试更多,如果我发现它不能以某种方式工作,我会尝试回到这里更新.

(已编辑 - 现在提供适当的解决方案作为答案)

GPW*_*GPW 23

我最终把解决方案放在一起,所以在用户总是学习的建议我编辑了我的帖子,我把它作为一个实际答案.

好的,这可以做得很好.首先,您需要使用我在上面的编辑中指出的身份验证选项 - 这很好.然后你需要使用services.AddIdentityCore<TUser>()而不是services.AddIdentity<TUser>().然而,这并没有为角色管理添加大量内容,并且显然缺少适当的构造函数来为其提供您想要使用的角色类型.这意味着在我的情况下我必须这样做:

  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>>();
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
Run Code Online (Sandbox Code Playgroud)

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

我希望这在某些方面对某人有帮助.

  • 你肯定帮了我 非常感谢。当您没有JWT或Identity的经验时,互联网上有许多过时的教程,很难找到解决方案。 (2认同)
  • @GPW 尽管我遇到的所有帖子都使用了重新实例化方法,但它似乎可以正常工作而无需重新实例化 IdentityBuilder 变量。var builder = services.AddIdentityCore&lt;ApplicationUser&gt;(o =&gt; … ) .AddRoles&lt;IdentityRole&gt;() .AddEntityFrameworkStores&lt;ApplicationDbContext&gt;() (2认同)