c# - bearer - JWT en.NET Core 2.0
jwt authentication with asp net core 2 web api (6)
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 AD; si algo está mal, ¡hágamelo saber!
-
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 Audiencia en la API -
ClientId
se llama 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é ??), que se encuentra en Azure Active Directory> Propiedades -
Si implementa la API como una aplicación web alojada de Azure, asegúrese de establecer la configuración de la aplicación:
p.ej. AzureAD: Audiencia / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
He estado en una gran aventura para lograr que JWT funcione en DotNet core 2.0 (ahora llega a la versión final hoy). Hay una gran cantidad de documentación, pero todo el código de muestra parece estar usando API obsoletas y está llegando a Core, es realmente vertiginoso descubrir cómo se supone que debe implementarse. Intenté usar Jose, pero la aplicación. UseJwtBearerAuthentication ha quedado en desuso 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 desde el encabezado de autorización y me permita autorizar solicitudes para un token JWT codificado HS256?
La siguiente clase no arroja ninguna excepción, pero no hay solicitudes autorizadas, 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 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;
}
}
}
Aquí hay una muestra mínima de trabajo completo con un controlador. Espero que puedas verificarlo usando Postman o 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 proyectos reales, 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 necesita tomarla exactamente del entorno. Puede usar la configuración. Sin embargo, en su lugar podemos escribir esta variable en 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 valores para pasar nombre de usuario y 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 ():
app.UseAuthentication();
-
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! ¡Aclamaciones!
ACTUALIZACIÓN: La gente pregunta cómo obtener el usuario actual. Que hacer:
-
En Startup.cs en ConfigureServices () agregar
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
-
En un controlador, agregue al constructor:
private readonly int _currentUser; public MyController(IHttpContextAccessor httpContextAccessor) { _currentUser = httpContextAccessor.CurrentUser(); }
-
Agregue una extensión en algún lugar 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; } }
Aquí hay una solución para ti.
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
app.UseAuthentication();
ahora puede usarlo en su controlador agregando atributo
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public IActionResult GetUserInfo()
{
Para obtener detalles completos del código fuente que utiliza angular como extremo frontal, consulte here
Mi
tokenValidationParameters
funciona 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 opciones.RequireHttpsMetadata = false así:
.AddJwtBearer(options =>
{
options.TokenValidationParameters =tokenValidationParameters
options.RequireHttpsMetadata = false;
});
EDITAR :
No olvides llamar
app.UseAuthentication();
en Startup.cs -> Configurar método antes de app.UseMvc ();
Solo para actualizar la excelente respuesta de @alerya tuve que modificar la clase auxiliar 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 podría obtener el ID de usuario en mi capa de servicio. Sé que es fácil en el controlador, pero un desafío más abajo.
Implementación de autenticación de token de portador Asp.net Core 2.0 JWT 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 Configure ()
// ===== Use Authentication ======
app.UseAuthentication();
User.cs // Es una clase de 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();
}
}
Pase TokenType y AccessToken en el encabezado de otros servicios web.
¡La mejor de las suertes! Solo soy principiante. Solo pasé una semana para comenzar a aprender asp.net core.