He estado en una gran aventura para que JWT funcione en DotNet core 2.0 (que ahora llega a la versión final hoy). Hay un montón de documentación, pero todo el código de ejemplo parece ser el uso de las API en desuso y que viene en fresco a Core, es vertiginoso positivamente a averiguar cómo exactamente se supone que debe ser implementado. Intenté usar Jose, pero app. UseJwtBearerAuthentication ha quedado obsoleto y no hay documentación sobre qué hacer a continuación.
¿Alguien tiene un proyecto de código abierto que use dotnet core 2.0 que simplemente pueda analizar un JWT del encabezado de autorización y permitirme autorizar solicitudes para un token JWT codificado en HS256?
La clase a continuación no arroja ninguna excepción, pero no se autorizan solicitudes y no obtengo ninguna indicación de por qué no están autorizadas. Las respuestas son 401 vacías, por lo que para mí eso indica que no hubo una excepción, pero que el secreto no coincide.
Una cosa extraña es que mis tokens están encriptados con el algoritmo HS256, pero no veo ningún indicador que le diga que lo obligue a usar ese algoritmo en cualquier lugar.
Aquí está la clase que tengo hasta ahora:
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;
}
}
}
Respuestas:
Aquí hay una muestra mínima de trabajo completa con un controlador. Espero que puedas comprobarlo usando Postman o una llamada JavaScript.
appsettings.json, appsettings.Development.json. Agrega una sección. Tenga en cuenta que la clave debe ser bastante larga y el emisor es una dirección del servicio:
... ,"Tokens": { "Key": "Rather_very_long_key", "Issuer": "http://localhost:56268/" } ...
!!! En un proyecto real, no guarde la clave en el archivo appsettings.json. Debe mantenerse en la variable de entorno y tomarlo así:
Environment.GetEnvironmentVariable("JWT_KEY");
ACTUALIZACIÓN : Al ver cómo funciona la configuración principal de .net, no es necesario que la extraiga exactamente del entorno. Puede utilizar la configuración. Sin embargo, en su lugar, podemos escribir esta variable en las variables de entorno en producción, entonces nuestro código preferirá las variables de entorno en lugar de la configuración.
AuthRequest.cs: Dto mantener los valores para pasar el nombre de usuario y la contraseña:
public class AuthRequest { public string UserName { get; set; } public string Password { get; set; } }
Startup.cs en el método Configure () ANTES de app.UseMvc ():
Startup.cs en 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"])) }; });
Agregar un controlador:
[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"); }}
¡Eso es todo amigos! ¡Salud!
ACTUALIZACIÓN: La gente pregunta cómo obtener Usuario actual. Que hacer:
En Startup.cs en ConfigureServices () agregue
En un controlador, agregue al constructor:
private readonly int _currentUser; public MyController(IHttpContextAccessor httpContextAccessor) { _currentUser = httpContextAccessor.CurrentUser(); }
Agregue en algún lugar una extensión y úsela en su controlador (usando ....)
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; } }
fuente
Mis
tokenValidationParameters
obras cuando se ven así:var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = GetSignInKey(), ValidateIssuer = true, ValidIssuer = GetIssuer(), ValidateAudience = true, ValidAudience = GetAudience(), ValidateLifetime = true, ClockSkew = TimeSpan.Zero };
y
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"; }
Además, agregue options.RequireHttpsMetadata = false así:
.AddJwtBearer(options => { options.TokenValidationParameters =tokenValidationParameters options.RequireHttpsMetadata = false; });
EDITAR :
No olvides llamar
en Startup.cs -> Configurar método antes de app.UseMvc ();
fuente
app.UseAuthentication();
nota que se ha llamado antes,app.UseMvc();
si no lo hace, obtendrá 401 incluso cuando el token se haya autorizado con éxito; ¡pasé aproximadamente 2 días trabajando en eso!Implementación de autenticación de token de portador JWT de Asp.net Core 2.0 con demostración de API web
Agregar paquete " 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 Configurar ()
// ===== Use Authentication ====== app.UseAuthentication();
User.cs // Es una clase modelo solo por ejemplo. Puede ser cualquier cosa.
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 // Es solo una clase de contexto. Puede ser cualquier cosa.
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(); } }
Prueba en PostMan:
Pase TokenType y AccessToken en el encabezado en otros servicios web.
¡La mejor de las suertes! Solo soy un principiante. Solo pasé una semana para comenzar a aprender el núcleo de asp.net.
fuente
Aquí tienes una solución.
En su startup.cs, en primer lugar, configúrelo como servicios:
services.AddAuthentication().AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters() { IssuerSigningKey = "somethong", ValidAudience = "something", : }; });
segundo, llame a estos servicios en la configuración
ahora puedes usarlo en tu controlador agregando atributo
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpGet] public IActionResult GetUserInfo() {
Para obtener todos los detalles del código fuente que utiliza angular como Frond-end, consulte aquí
fuente
services.AddAuthorization
función en el inicio.Aquí está mi implementación para una 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" }, ... }
El código anterior habilita la autenticación en todos los controladores. Para permitir el acceso anónimo, puede decorar un controlador completo:
[Route("api/[controller]")] [AllowAnonymous] public class AnonymousController : Controller { ... }
o simplemente decorar un método para permitir un único punto final:
[AllowAnonymous] [HttpPost("anonymousmethod")] public async Task<IActionResult> MyAnonymousMethod() { ... }
Notas:
Este es mi primer intento de autenticación de AD. Si hay algún problema, ¡avíseme!
Audience
debe coincidir con el ID de recurso solicitado por el cliente. En nuestro caso, nuestro cliente (una aplicación web Angular) se registró por separado en Azure AD y usó su Id. De cliente, que registramos como Audience en la API.ClientId
se denomina Id. de aplicación en Azure Portal (¿por qué?), el Id. de aplicación del registro de la aplicación para la API.TenantId
se llama Id. de directorio en Azure Portal (¿por qué?), se encuentra en Azure Active Directory> Propiedades.Si implementa la API como una aplicación web alojada en Azure, asegúrese de establecer la configuración de la aplicación:
p.ej. AzureAD: Audiencia / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
fuente
Solo para actualizar la excelente respuesta de @alerya, tuve que modificar la clase de ayuda para que se vea así;
public static class IHttpContextAccessorExtension { public static string CurrentUser(this IHttpContextAccessor httpContextAccessor) { var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; return userId; } }
Entonces pude obtener el ID de usuario en mi capa de servicio. Sé que es fácil en el controlador, pero un desafío más adelante.
fuente