Byłem na niezłej przygodzie, aby JWT działał na DotNet core 2.0 (teraz osiągam ostateczne wydanie dzisiaj). Jest mnóstwo dokumentacji, ale wydaje się, że cały przykładowy kod korzysta z przestarzałych interfejsów API i pojawia się na nowo w Core. To naprawdę oszałamiające, gdy zastanawiasz się, jak dokładnie ma zostać wdrożony. Próbowałem użyć Jose, ale aplikacja. UseJwtBearerAuthentication zostało uznane za przestarzałe i nie ma dokumentacji na temat dalszych działań.
Czy ktoś ma projekt typu open source, który używa dotnet core 2.0, który może po prostu przeanalizować JWT z nagłówka autoryzacji i pozwolić mi na autoryzowanie żądań tokenu JWT zakodowanego HS256?
Poniższa klasa nie zgłasza żadnych wyjątków, ale żadne żądania nie są autoryzowane i nie mam żadnych wskazówek, dlaczego są nieautoryzowane. Odpowiedzi są puste 401, więc dla mnie oznacza to, że nie było wyjątku, ale sekret nie pasuje.
Dziwną rzeczą jest to, że moje tokeny są szyfrowane algorytmem HS256, ale nie widzę wskaźnika, który kazałby mu zmusić go do użycia tego algorytmu w dowolnym miejscu.
Oto klasa, którą mam do tej pory:
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;
}
}
}
Odpowiedzi:
Oto pełna działająca minimalna próbka z kontrolerem. Mam nadzieję, że możesz to sprawdzić za pomocą wywołania Postman lub JavaScript.
appsettings.json, appsettings.Development.json. Dodaj sekcję. Uwaga, klucz powinien być dość długi, a wystawca to adres usługi:
... ,"Tokens": { "Key": "Rather_very_long_key", "Issuer": "http://localhost:56268/" } ...
!!! W prawdziwym projekcie nie przechowuj klucza w pliku appsettings.json. Powinien być przechowywany w zmiennej środowiskowej i potraktować to w ten sposób:
Environment.GetEnvironmentVariable("JWT_KEY");
AKTUALIZACJA : Widząc, jak działają ustawienia .net core, nie musisz pobierać ich dokładnie ze środowiska. Możesz użyć ustawienia. Jednak zamiast tego możemy zapisać tę zmienną do zmiennych środowiskowych w środowisku produkcyjnym, wtedy nasz kod będzie preferował zmienne środowiskowe zamiast konfiguracji.
AuthRequest.cs: Dto przechowuje wartości do przekazywania loginu i hasła:
public class AuthRequest { public string UserName { get; set; } public string Password { get; set; } }
Startup.cs w metodzie Configure () PRZED app.UseMvc ():
Startup.cs w 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"])) }; });
Dodaj kontroler:
[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"); }}
To wszystko ludzie! Twoje zdrowie!
AKTUALIZACJA: Ludzie pytają, jak uzyskać obecnego użytkownika. Do zrobienia:
W Startup.cs w ConfigureServices () dodaj
W kontrolerze dodaj do konstruktora:
private readonly int _currentUser; public MyController(IHttpContextAccessor httpContextAccessor) { _currentUser = httpContextAccessor.CurrentUser(); }
Dodaj gdzieś rozszerzenie i używaj go w swoim kontrolerze (używając ...)
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; } }
źródło
Moje
tokenValidationParameters
prace, gdy wyglądają tak:var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = GetSignInKey(), ValidateIssuer = true, ValidIssuer = GetIssuer(), ValidateAudience = true, ValidAudience = GetAudience(), ValidateLifetime = true, ClockSkew = TimeSpan.Zero };
i
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"; }
Ponadto dodaj opcje.RequireHttpsMetadata = false w ten sposób:
.AddJwtBearer(options => { options.TokenValidationParameters =tokenValidationParameters options.RequireHttpsMetadata = false; });
EDYCJA :
Nie zapomnij zadzwonić
w Startup.cs -> Configure method before app.UseMvc ();
źródło
app.UseAuthentication();
notatkę wywołaną wcześniej,app.UseMvc();
jeśli tego nie zrobisz, otrzymasz 401, nawet jeśli token zostanie pomyślnie autoryzowany - poświęciłem na to około 2 dni!Asp.net Core 2.0 JWT Bearer Token Authentication with Web Api Demo
Dodaj pakiet „ 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 Konfiguruj ()
// ===== Use Authentication ====== app.UseAuthentication();
User.cs // To jest przykładowa klasa. To może być wszystko.
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 // To tylko klasa kontekstu. To może być wszystko.
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(); } }
Test na PostMan:
Przekaż TokenType i AccessToken w nagłówku w innych usługach internetowych.
Powodzenia! Jestem tylko początkującym. Spędziłem tylko tydzień, aby rozpocząć naukę asp.net core.
źródło
Oto rozwiązanie dla Ciebie.
Najpierw w pliku startup.cs skonfiguruj go jako usługi:
services.AddAuthentication().AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters() { IssuerSigningKey = "somethong", ValidAudience = "something", : }; });
po drugie, wywołaj te usługi w config
teraz możesz go użyć w swoim kontrolerze, dodając atrybut
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpGet] public IActionResult GetUserInfo() {
Aby uzyskać szczegółowe informacje na temat kodu źródłowego używającego angular jako Frond-end, zobacz tutaj
źródło
services.AddAuthorization
funkcji w uruchomieniu.Oto moja implementacja dla interfejsu 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" }, ... }
Powyższy kod umożliwia autoryzację na wszystkich kontrolerach. Aby umożliwić anonimowy dostęp, możesz udekorować cały kontroler:
[Route("api/[controller]")] [AllowAnonymous] public class AnonymousController : Controller { ... }
lub po prostu udekoruj metodę, aby umożliwić pojedynczy punkt końcowy:
[AllowAnonymous] [HttpPost("anonymousmethod")] public async Task<IActionResult> MyAnonymousMethod() { ... }
Uwagi:
To moja pierwsza próba autoryzacji AD - jeśli coś jest nie tak, daj mi znać!
Audience
musi być zgodny z identyfikatorem zasobu żądanym przez klienta. W naszym przypadku nasz klient (aplikacja internetowa Angular) został zarejestrowany osobno w usłudze Azure AD i użył swojego identyfikatora klienta, który zarejestrowaliśmy jako odbiorcę w interfejsie APIClientId
jest nazywany identyfikatorem aplikacji w portalu Azure (dlaczego ??), identyfikatorem aplikacji rejestracji aplikacji dla interfejsu API.TenantId
nazywa się Directory ID w Azure Portal (dlaczego ??) i znajduje się w Azure Active Directory> PropertiesW przypadku wdrażania interfejsu API jako aplikacji internetowej hostowanej na platformie Azure, upewnij się, że ustawiono ustawienia aplikacji:
na przykład. AzureAD: Audience / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
źródło
Aby zaktualizować doskonałą odpowiedź @alerya, musiałem zmodyfikować klasę pomocnika, aby wyglądała tak;
public static class IHttpContextAccessorExtension { public static string CurrentUser(this IHttpContextAccessor httpContextAccessor) { var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; return userId; } }
Wtedy mogłem uzyskać identyfikator użytkownika w mojej warstwie usług. Wiem, że w kontrolerze jest to łatwe, ale trudniejsze wyzwanie.
źródło