【问题标题】:JWT on .NET Core 2.0.NET Core 2.0 上的 JWT
【发布时间】:2018-01-22 23:57:21
【问题描述】:

我一直在冒险让 JWT 在 DotNet 核心 2.0 上工作(今天已达到最终版本)。有 ton 的文档,但所有示例代码似乎都在使用已弃用的 API 并且对 Core 来说是新鲜的,弄清楚它应该如何实现确实令人眼花缭乱。我尝试使用 Jose,但应用程序。 UseJwtBearerAuthentication 已被弃用,并且没有关于下一步做什么的文档。

是否有人拥有使用 dotnet core 2.0 的开源项目,该项目可以简单地从授权标头中解析 JWT,并允许我授权对 HS256 编码的 JWT 令牌的请求?

下面的类没有抛出任何异常,但是没有请求被授权,我没有得到任何指示为什么它们是未经授权的。响应是空的 401,所以对我来说这表明没有例外,但秘密不匹配。

奇怪的是,我的令牌是使用 HS256 算法加密的,但我没有看到任何指示符告诉它强制它在任何地方使用该算法。

这是我目前上过的课程:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },


                // Validate the token expiry
                ValidateLifetime = true,
            };

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


            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

【问题讨论】:

标签: c# .net-core jwt jose


【解决方案1】:

我的tokenValidationParameters 看起来像这样:

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

此外,像这样添加 options.RequireHttpsMetadata = false :

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

编辑

别忘了打电话

 app.UseAuthentication();

在 Startup.cs -> 配置方法 before app.UseMvc();

【讨论】:

  • 我敢打赌是 app.UseAuthentication();打电话就可以了,我不知道我需要那个。谢谢!
  • 我认为您还需要指定 ValidAudience、ValidIssuer 和 IssuerSigningKey。没有它对我来说是行不通的
  • 是的,就是这样。我需要添加 app.UseAuthentication() ,仅此而已。非常感谢!
  • +1 for app.UseAuthentication(); 注意在 app.UseMvc(); 之前被调用,如果你不这样做,即使令牌被成功授权,你也会得到 401 - 我花了大约 2 天的时间来解决这个问题!跨度>
  • "app.UseAuthentication();",我把.net core从1.0升级到2.0后花了一整天的时间来修复401问题,但是直到看到这个帖子才找到解决办法.谢谢阿德里安。
【解决方案2】:

这里有一个解决方案。

在您的 startup.cs 中,首先,将其配置为服务:

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

其次,在配置中调用这个服务

          app.UseAuthentication();

现在您可以通过添加属性在控制器中使用它

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

有关使用 Angular 作为 Frond-end 的完整详细源代码,请参阅here

【讨论】:

  • 这是拯救我培根的答案!能够使用 [Authorize] 就好了。想象一下这可以用 Startup.cs 处理
  • Simon,因为,您可以在同一个 asp.net 核心 mvc 应用程序中拥有多个方案,例如 services.AddAuthentication().AddCookie().AddJwtBearer();
  • 你也可以在启动时通过services.AddAuthorization函数设置一个默认的Authentication Scheme。
  • 为了跟进@NevilleNazerane 的声明,设置默认身份验证方案的代码(将与普通的 [Authorize] 装饰器一起使用)代码用于回答这个问题。它是 services.AddAuthentication(sharedOptions => { sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; })
  • 如果我尝试为 IssuerSigningKey 遵循此示例,则会收到错误无法将源类型“字符串”转换为目标类型“Microsoft.IdentityModel.Tokens.SecurityKey”
【解决方案3】:

这是我对 .Net Core 2.0 API 的实现:

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
            
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json:

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

以上代码在所有控制器上启用身份验证。要允许匿名访问,您可以装饰整个控制器:

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

或者只是装饰一个方法以允许单个端点:

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

注意事项:

  • 这是我第一次尝试 AD 身份验证 - 如果有任何问题,请告诉我!

  • Audience 必须与客户端请求的资源 ID 匹配。在我们的例子中,我们的客户端(一个 Angular Web 应用程序)在 Azure AD 中单独注册,它使用它的客户端 ID,我们在 API 中注册为受众

  • ClientId 在 Azure 门户中称为 Application ID(为什么??),即 API 应用注册的 Application ID。

  • TenantId 在 Azure 门户中称为 Directory ID(为什么??),位于 Azure Active Directory > 属性

  • 如果将 API 部署为 Azure 托管的 Web 应用程序,请确保设置应用程序设置:

    例如。 AzureAD:观众 / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx

【讨论】:

    【解决方案4】:

    这是带有控制器的完整工作最小示例。我希望您可以使用 Postman 或 JavaScript 调用来检查它。

    1. appsettings.json、appsettings.Development.json。添加一个部分。注意,Key 应该比较长,Issuer 是服务的地址:

      ...
      ,"Tokens": {
          "Key": "Rather_very_long_key",
          "Issuer": "http://localhost:56268/"
      }
      ...
      

      !!!在实际项目中,不要将 Key 保留在 appsettings.json 文件中。它应该保存在环境变量中并像这样:

      Environment.GetEnvironmentVariable("JWT_KEY");
      

    更新:了解 .net 核心设置的工作原理,您无需完全从环境中获取。您可以使用设置。但是,我们可以将此变量写入生产环境变量中,然后我们的代码将更喜欢环境变量而不是配置。

    1. AuthRequest.cs : Dto 保存传递登录名和密码的值:

      public class AuthRequest
      {
          public string UserName { get; set; }
          public string Password { get; set; }
      }
      
    2. 在 app.UseMvc() 之前的 Configure() 方法中的 Startup.cs :

      app.UseAuthentication();
      
    3. ConfigureServices() 中的 Startup.cs :

      services.AddAuthentication()
          .AddJwtBearer(cfg =>
          {
              cfg.RequireHttpsMetadata = false;
              cfg.SaveToken = true;
      
              cfg.TokenValidationParameters = new TokenValidationParameters()
              {
                  ValidIssuer = Configuration["Tokens:Issuer"],
                  ValidAudience = Configuration["Tokens:Issuer"],
                  IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
              };
      
          });
      
    4. 添加控制器:

          [Route("api/[controller]")]
          public class TokenController : Controller
          {
              private readonly IConfiguration _config;
              private readonly IUserManager _userManager;
      
              public TokenController(IConfiguration configuration, IUserManager userManager)
              {
                  _config = configuration;
                  _userManager = userManager;
              }
      
              [HttpPost("")]
              [AllowAnonymous]
              public IActionResult Login([FromBody] AuthRequest authUserRequest)
              {
                  var user = _userManager.FindByEmail(model.UserName);
      
                  if (user != null)
                  {
                      var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                      if (checkPwd)
                      {
                          var claims = new[]
                          {
                              new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                              new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                          };
      
                          var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                          var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
      
                          var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                          _config["Tokens:Issuer"],
                          claims,
                          expires: DateTime.Now.AddMinutes(30),
                          signingCredentials: creds);
      
                          return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                      }
                  }
      
                  return BadRequest("Could not create token");
              }}
      

    这就是所有的人!干杯!

    更新:人们问如何获取当前用户。待办事项:

    1. 在 Startup.cs 的 ConfigureServices() 中添加

      services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
      
    2. 在控制器中添加到构造函数:

      private readonly int _currentUser;
      public MyController(IHttpContextAccessor httpContextAccessor)
      {
         _currentUser = httpContextAccessor.CurrentUser();
      }
      
    3. 在某处添加扩展并在控制器中使用它(使用 ....)

      public static class IHttpContextAccessorExtension
      {
          public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
          {
              var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
              int.TryParse(stringId ?? "0", out int userId);
      
              return userId;
          }
      }
      

    【讨论】:

    • 这对我很有帮助。我唯一不清楚的是如何检查令牌以进行后续调用,或者如何确定当前登录的用户是谁。
    • 这真的很简单。在控制器的方法调用中: var currentUser = HttpContext.User.Identity.Name;干杯!
    • 老兄,这真是一个巨大的帮助,非常感谢你的详细回答!
    • 感谢您提供以下信息。它使我免于 401 Unauthorized 错误。 3.Startup.cs 在 Configure() 方法 BEFORE app.UseMvc() : app.UseAuthentication();
    • 这是一个矛盾,我们不应该将密钥存储在 appsettings 文件中,但我们在 Configuration["Tokens:Key"]) 中检索它。如果我们有一个不同的服务可以安全地检索密钥,我们将如何将其合并到 ConfigureServices 中?
    【解决方案5】:

    Asp.net Core 2.0 JWT Bearer Token Authentication implementation with Web Api Demo

    添加包“Microsoft.AspNetCore.Authentication.JwtBearer

    Startup.cs ConfigureServices()

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(cfg =>
                {
                    cfg.RequireHttpsMetadata = false;
                    cfg.SaveToken = true;
    
                    cfg.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = "me",
                        ValidAudience = "you",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
                    };
    
                });
    

    Startup.cs Configure()

    // ===== Use Authentication ======
            app.UseAuthentication();
    

    User.cs // 例如模型类。它可以是任何东西。

    public class User
    {
        public Int32 Id { get; set; }
        public string Username { get; set; }
        public string Country { get; set; }
        public string Password { get; set; }
    }
    

    UserContext.cs // 只是上下文类。它可以是任何东西。

    public class UserContext : DbContext
    {
        public UserContext(DbContextOptions<UserContext> options) : base(options)
        {
            this.Database.EnsureCreated();
        }
    
        public DbSet<User> Users { get; set; }
    }
    

    AccountController.cs

    [Route("[controller]")]
    public class AccountController : Controller
    {
    
        private readonly UserContext _context;
    
        public AccountController(UserContext context)
        {
            _context = context;
        }
    
        [AllowAnonymous]
        [Route("api/token")]
        [HttpPost]
        public async Task<IActionResult> Token([FromBody]User user)
        {
            if (!ModelState.IsValid) return BadRequest("Token failed to generate");
            var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
                if (userIdentified == null)
                {
                    return Unauthorized();
                }
                user = userIdentified;
    
            //Add Claims
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
                new Claim(JwtRegisteredClaimNames.Sub, "data"),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            };
    
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken("me",
                "you",
                claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);
    
            return Ok(new
            {
                access_token = new JwtSecurityTokenHandler().WriteToken(token),
                expires_in = DateTime.Now.AddMinutes(30),
                token_type = "bearer"
            });
        }
    }
    

    UserController.cs

    [Authorize]
    [Route("api/[controller]")]
    public class UserController : ControllerBase
    {
        private readonly UserContext _context;
    
        public UserController(UserContext context)
        {
            _context = context;
            if(_context.Users.Count() == 0 )
            {
                _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
                _context.SaveChanges();
            }
        }
    
        [HttpGet("[action]")]
        public IEnumerable<User> GetList()
        {
            return _context.Users.ToList();
        }
    
        [HttpGet("[action]/{id}", Name = "GetUser")]
        public IActionResult GetById(long id)
        {
            var user = _context.Users.FirstOrDefault(u => u.Id == id);
            if(user == null)
            {
                return NotFound();
            }
            return new ObjectResult(user);
        }
    
    
        [HttpPost("[action]")]
        public IActionResult Create([FromBody] User user)
        {
            if(user == null)
            {
                return BadRequest();
            }
    
            _context.Users.Add(user);
            _context.SaveChanges();
    
            return CreatedAtRoute("GetUser", new { id = user.Id }, user);
    
        }
    
        [HttpPut("[action]/{id}")]
        public IActionResult Update(long id, [FromBody] User user)
        {
            if (user == null)
            {
                return BadRequest();
            }
    
            var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
            if (userIdentified == null)
            {
                return NotFound();
            }
    
            userIdentified.Country = user.Country;
            userIdentified.Username = user.Username;
    
            _context.Users.Update(userIdentified);
            _context.SaveChanges();
            return new NoContentResult();
        }
    
    
        [HttpDelete("[action]/{id}")]
        public IActionResult Delete(long id)
        {
            var user = _context.Users.FirstOrDefault(u => u.Id == id);
            if (user == null)
            {
                return NotFound();
            }
    
            _context.Users.Remove(user);
            _context.SaveChanges();
    
            return new NoContentResult();
        }
    }
    

    对 PostMan 的测试:

    在其他 web 服务的 Header 中传递 TokenType 和 AccessToken。

    祝你好运!我只是初学者。我只花了一个星期就开始学习asp.net core。

    【讨论】:

    • 我收到 InvalidOperationException:尝试激活“AccountController”时无法解析“WebApplication8.UserContext”类型的服务。当我尝试邮递员调用 Post to account/api/token
    【解决方案6】:

    只是为了更新@alerya 的出色答案,我不得不修改帮助器类使其看起来像这样;

    public static class IHttpContextAccessorExtension
        {
            public static string CurrentUser(this IHttpContextAccessor httpContextAccessor)
            {           
                var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
                return userId;
            }
        }
    

    然后我可以在我的服务层获取 userId。我知道在控制器中这很容易,但在更远的地方是一个挑战。

    【讨论】:

      猜你喜欢
      • 2019-01-21
      • 2018-12-21
      • 2018-11-08
      • 2018-10-07
      • 2018-06-22
      • 2019-06-12
      • 1970-01-01
      • 2018-11-20
      • 2018-03-24
      相关资源
      最近更新 更多