JWT на .NET Core 2.0

Я был в довольно приключении, чтобы заставить JWT работать над DotNet core 2.0 (теперь он достиг финальной версии сегодня). Существует тонна документации, но все примеры кода, похоже, используют устаревшие API и приходят в новый для Core. Удивительно головокружительно, чтобы выяснить, как именно он должен быть реализован. Я пробовал использовать Хосе, но приложение. UseJwtBearerAuthentication устарела, и нет документации о том, что делать дальше.

Кто-нибудь имеет проект с открытым исходным кодом, который использует dotnet core 2.0, который может просто проанализировать JWT из заголовка авторизации и разрешить мне разрешать запросы на токен JWT, кодированный HS256?

В приведенном ниже classе не выбрасываются никакие исключения, но запросы не разрешены, и я не вижу никаких указаний, почему они неавторизированы. Ответы пустые 401, поэтому для меня это означает, что исключение не было, но секрет не соответствует.

Самое странное, что мои токены зашифрованы алгоритмом HS256, но я не вижу индикатора, чтобы заставить его использовать этот алгоритм где угодно.

Вот class, который у меня есть до сих пор:

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

Вот полный рабочий минимум с controllerом. Надеюсь, вы сможете проверить это, используя Postman или JavaScript-вызов.

  1. appsettings.json, appsettings.Development.json. Добавьте раздел. Примечание. Ключ должен быть довольно длинным, а Эмитент является адресом услуги:

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

    !!! В реальном проекте не храните ключ в файле appsettings.json. Он должен храниться в переменной Environment и принимать его так:

     Environment.GetEnvironmentVariable("JWT_KEY"); 

ОБНОВЛЕНИЕ . Увидев, как работают основные параметры ядра .net, вам не нужно принимать его точно из среды. Вы можете использовать настройку. Однако вместо этого мы можем записать эту переменную в переменные среды в процессе производства, тогда наш код предпочтет переменные окружения вместо конфигурации.

  1. AuthRequest.cs: Dto сохраняет значения для прохождения логина и пароля:

     public class AuthRequest { public string UserName { get; set; } public string Password { get; set; } } 
  2. Startup.cs в методе Configure () ПЕРЕД app.UseMvc ():

     app.UseAuthentication(); 
  3. Startup.cs в ConfigureServices ():

     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. Добавить controller:

      [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(); 
  2. В controllerе добавьте в конструктор:

     private readonly int _currentUser; public MyController(IHttpContextAccessor httpContextAccessor) { _currentUser = httpContextAccessor.CurrentUser(); } 
  3. Добавьте где-нибудь расширение и используйте его в своем controllerе (используя …)

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

Это все люди.

Мои 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; }); 

EDIT :

Не забудьте позвонить

  app.UseAuthentication(); 

в Startup.cs -> Настроить метод перед app.UseMvc ();

Вот вам решение.

В вашем startup.cs, во-первых, настройте его как службы:

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

во-вторых, вызовите эти службы в config

  app.UseAuthentication(); 

теперь вы можете использовать его в своем controllerе, добавив атрибут

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

Для получения полной информации о исходном коде, который используется угловым как Frond-end, см. Здесь

Вот моя реализация для API .Net Core 2.0:

  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" }, ... } 

Вышеприведенный код позволяет auth на всех controllerах. Чтобы разрешить анонимный доступ, вы можете украсить весь controller:

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

или просто украсить метод, позволяющий создать единую конечную точку:

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

Заметки:

  • Это моя первая попытка аутентификации AD – если что-то не так, сообщите мне!
  • Audience должна соответствовать идентификатору ресурса, запрошенному клиентом. В нашем случае наш клиент (Угловое веб-приложение) был зарегистрирован отдельно в Azure AD, и он использовал свой идентификатор клиента, который мы зарегистрировали как Аудитория в API
  • ClientId называется Application ID на портале Azure (почему?), Идентификатор приложения для регистрации приложения для API.
  • TenantId называется идентификатором каталога на портале Azure (почему?), Найденным в Azure Active Directory> Свойства
  • Если развертывание API в качестве веб-приложения, размещенного в Azure, убедитесь, что вы установили параметры приложения:

    например. AzureAD: Аудитория / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Asp.net Core 2.0 JWT Поддержка аутентификации маркера несущей с использованием 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 // Это class модели, например. Это может быть что угодно.

 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 // Это просто class контекста. Это может быть что угодно.

 public class UserContext : DbContext { public UserContext(DbContextOptions options) : base(options) { this.Database.EnsureCreated(); } public DbSet 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 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 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: В ответ вы получите токен.

Pass TokenType и AccessToken в заголовке в других веб-сервисах. введите описание изображения здесь

Удачи! Я просто начинаю. Я потратил одну неделю, чтобы начать изучение ядра asp.net.

Просто чтобы обновить отличный ответ от @alerya, мне пришлось изменить class-помощник, чтобы он выглядел так:

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

Тогда я мог бы получить userId в своем сервисе. Я знаю, что в controllerе легко, но проблема еще ниже.

Interesting Posts
Давайте будем гением компьютера.