c# - basada - json web token asp net core web api
Autenticación basada en tokens en ASP.NET Core(actualizado) (5)
Esto es realmente un duplicado de otra respuesta mía , que tiendo a mantener más actualizada a medida que recibe más atención. ¡Los comentarios allí también pueden ser útiles para usted!
Actualizado para .Net Core 2:
Las versiones anteriores de esta respuesta usaban RSA;
Realmente no es necesario si el mismo código que genera los tokens también verifica los tokens.
Sin embargo, si está distribuyendo la responsabilidad, es probable que aún desee hacerlo utilizando una instancia de
Microsoft.IdentityModel.Tokens.RsaSecurityKey
.
-
Crea algunas constantes que usaremos más tarde; esto es lo que hice:
const string TokenAudience = "Myself"; const string TokenIssuer = "MyProject";
-
Agregue esto a los servicios de
ConfigureServices
su Startup.cs. Usaremos la inyección de dependencia más tarde para acceder a esta configuración. Supongo que suConfigurationSection
authenticationConfiguration
es un objetoConfigurationSection
oConfiguration
modo que puede tener una configuración diferente para la depuración y la producción. ¡Asegúrese de guardar su clave de forma segura! Puede ser cualquier cadena.var keySecret = authenticationConfiguration["JwtSigningKey"]; var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret)); services.AddTransient(_ => new JwtSignInHandler(symmetricKey)); services.AddAuthentication(options => { // This causes the default authentication scheme to be JWT. // Without this, the Authorization header is not checked and // you''ll get no results. However, this also means that if // you''re already using cookies in your app, they won''t be // checked by default. options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters.ValidateIssuerSigningKey = true; options.TokenValidationParameters.IssuerSigningKey = symmetricKey; options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience; options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer; });
He visto otras respuestas cambiar otras configuraciones, como
ClockSkew
; los valores predeterminados se configuran de manera tal que funcionen para entornos distribuidos cuyos relojes no están exactamente sincronizados. Estas son las únicas configuraciones que necesita cambiar. -
Configurar autenticación. Debería tener esta línea antes de cualquier middleware que requiera su información de
User
, comoapp.UseMvc()
.app.UseAuthentication();
Tenga en cuenta que esto no hará que su token se emita con
SignInManager
o cualquier otra cosa. Deberá proporcionar su propio mecanismo para generar su JWT; consulte a continuación. -
Es posible que desee especificar una
AuthorizationPolicy
. Esto le permitirá especificar controladores y acciones que solo permiten tokens de portador como autenticación utilizando[Authorize("Bearer")]
.services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType) .RequireAuthenticatedUser().Build()); });
-
Aquí viene la parte difícil: construir el token.
class JwtSignInHandler { public const string TokenAudience = "Myself"; public const string TokenIssuer = "MyProject"; private readonly SymmetricSecurityKey key; public JwtSignInHandler(SymmetricSecurityKey symmetricKey) { this.key = symmetricKey; } public string BuildJwt(ClaimsPrincipal principal) { var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: TokenIssuer, audience: TokenAudience, claims: principal.Claims, expires: DateTime.Now.AddMinutes(20), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } }
Luego, en su controlador donde desea su token, algo como lo siguiente:
[HttpPost] public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory) { var principal = new System.Security.Claims.ClaimsPrincipal(new[] { new System.Security.Claims.ClaimsIdentity(new[] { new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User") }) }); return tokenFactory.BuildJwt(principal); }
Aquí, supongo que ya tienes un director. Si está utilizando Identity, puede usar
IUserClaimsPrincipalFactory<>
para transformar suUser
en unClaimsPrincipal
. -
Para probarlo : Obtenga un token, póngalo en el formulario en jwt.io ¡Las instrucciones que proporcioné anteriormente también le permiten usar el secreto de su configuración para validar la firma!
-
Si estaba renderizando esto en una vista parcial en su página HTML en combinación con la autenticación de solo portador en .Net 4.5, ahora puede usar un
ViewComponent
para hacer lo mismo. Es casi lo mismo que el código de acción del controlador anterior.
Estoy trabajando con la aplicación ASP.NET Core. Estoy tratando de implementar la autenticación basada en tokens, pero no puedo entender cómo usar el nuevo sistema de seguridad .
Mi escenario: un cliente solicita un token. Mi servidor debe autorizar al usuario y devolver access_token, que será utilizado por el cliente en las siguientes solicitudes.
Aquí hay dos excelentes artículos sobre cómo implementar exactamente lo que necesito:
El problema es que no es obvio para mí cómo hacer lo mismo en ASP.NET Core.
Mi pregunta es: ¿cómo configurar la aplicación ASP.NET Core Web Api para que funcione con autenticación basada en token? ¿Qué dirección debo seguir? ¿Has escrito algún artículo sobre la versión más reciente o sabes dónde puedo encontrar uno?
¡Gracias!
Para lograr lo que describe, necesitará un servidor de autorización OAuth2 / OpenID Connect y un token de acceso de validación de middleware para su API.
Katana solía ofrecer un
OAuthAuthorizationServerMiddleware
, pero ya no existe en ASP.NET Core.
Sugiero echar un vistazo a
AspNet.Security.OpenIdConnect.Server
, una bifurcación experimental del middleware del servidor de autorización OAuth2 que se usa en el tutorial que mencionó: hay una versión OWIN / Katana 3 y una versión ASP.NET Core que admite ambos
net451
(.NET Desktop) y
netstandard1.4
(compatible con .NET Core).
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server
No se pierda el ejemplo de MVC Core que muestra cómo configurar un servidor de autorización OpenID Connect utilizando AspNet.Security.OpenIdConnect.Server y cómo validar los tokens de acceso cifrados emitidos por el middleware del servidor: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/Mvc.Server/Startup.cs
También puede leer esta publicación de blog, que explica cómo implementar la concesión de contraseña del propietario del recurso, que es el equivalente OAuth2 de autenticación básica: http://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-implementing-the-resource-owner-password-credentials-grant/
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
}
public void Configure(IApplicationBuilder app)
{
// Add a new middleware validating the encrypted
// access tokens issued by the OIDC server.
app.UseOAuthValidation();
// Add a new middleware issuing tokens.
app.UseOpenIdConnectServer(options =>
{
options.TokenEndpointPath = "/connect/token";
// Override OnValidateTokenRequest to skip client authentication.
options.Provider.OnValidateTokenRequest = context =>
{
// Reject the token requests that don''t use
// grant_type=password or grant_type=refresh_token.
if (!context.Request.IsPasswordGrantType() &&
!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only grant_type=password and refresh_token " +
"requests are accepted by this
return Task.FromResult(0);
}
// Since there''s only one application and since it''s a public client
// (i.e a client that cannot keep its credentials private),
// call Skip() to inform the server the request should be
// accepted without enforcing client authentication.
context.Skip();
return Task.FromResult(0);
};
// Override OnHandleTokenRequest to support
// grant_type=password token requests.
options.Provider.OnHandleTokenRequest = context =>
{
// Only handle grant_type=password token requests and let the
// OpenID Connect server middleware handle the other grant types.
if (context.Request.IsPasswordGrantType())
{
// Do your credentials validation here.
// Note: you can call Reject() with a message
// to indicate that authentication failed.
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique id]");
// By default, claims are not serialized
// in the access and identity tokens.
// Use the overload taking a "destinations"
// parameter to make sure your claims
// are correctly inserted in the appropriate tokens.
identity.AddClaim("urn:customclaim", "value",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes("profile", "offline_access");
context.Validate(ticket);
}
return Task.FromResult(0);
};
});
}
}
proyecto.json
{
"dependencies": {
"AspNet.Security.OAuth.Validation": "1.0.0",
"AspNet.Security.OpenIdConnect.Server": "1.0.0"
}
}
¡Buena suerte!
Puede echar un vistazo a las muestras de conexión de OpenId que ilustran cómo tratar con diferentes mecanismos de autenticación, incluidos los tokens JWT:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples
Si observa el proyecto Cordova Backend, la configuración de la API es así:
app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")),
branch => {
branch.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.RequireHttpsMetadata = false;
options.Audience = "localhost:54540";
options.Authority = "localhost:54540";
});
});
También vale la pena echar un vistazo a la lógica en /Providers/AuthorizationProvider.cs y el RessourceController de ese proyecto;).
Además, he implementado una aplicación de una sola página con implementación de autenticación basada en token utilizando el marco frontal Aurelia y el núcleo ASP.NET. También hay una señal R de conexión persistente. Sin embargo, no he hecho ninguna implementación de DB. El código se puede ver aquí: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth
Espero que esto ayude,
Mejor,
Alex
Puede usar
OpenIddict
para servir los tokens (iniciar sesión) y luego usar
UseJwtBearerAuthentication
para validarlos cuando se accede a una API / Controlador.
Esta es esencialmente toda la configuración que necesita en
Startup.cs
:
ConfigureServices:
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
// this line is added for OpenIddict to plug in
.AddOpenIddictCore<Application>(config => config.UseEntityFramework());
Configurar
app.UseOpenIddictCore(builder =>
{
// here you tell openiddict you''re wanting to use jwt tokens
builder.Options.UseJwtTokens();
// NOTE: for dev consumption only! for live, this is not encouraged!
builder.Options.AllowInsecureHttp = true;
builder.Options.ApplicationCanDisplayErrors = true;
});
// use jwt bearer authentication to validate the tokens
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.RequireHttpsMetadata = false;
// must match the resource on your token request
options.Audience = "http://localhost:58292/";
options.Authority = "http://localhost:58292/";
});
Hay una o dos cosas menores, como que su DbContext debe derivar de
OpenIddictContext<ApplicationUser, Application, ApplicationRole, string>
.
Puede ver una explicación completa (incluido el repositorio de github en funcionamiento) en esta publicación mía del blog: http://capesean.co.za/blog/asp-net-5-jwt-tokens/
Trabajando a partir de la fabulosa respuesta de Matt Dekrey , he creado un ejemplo completamente funcional de autenticación basada en token, trabajando contra ASP.NET Core (1.0.1). Puede encontrar el código completo en este repositorio en GitHub (ramas alternativas para 1.0.0-rc1 , beta8 , beta7 ), pero en resumen, los pasos importantes son:
Genere una clave para su aplicación
En mi ejemplo, genero una clave aleatoria cada vez que se inicia la aplicación, deberá generar una y almacenarla en algún lugar y proporcionarla a su aplicación. Vea este archivo para saber cómo estoy generando una clave aleatoria y cómo podría importarla desde un archivo .json . Como se sugiere en los comentarios de @kspearrin, la API de protección de datos parece un candidato ideal para administrar las claves "correctamente", pero aún no he resuelto si eso es posible. ¡Envíe una solicitud de extracción si lo resuelve!
Startup.cs - ConfigureServices
Aquí, necesitamos cargar una clave privada para que se firmen nuestros tokens, que también usaremos para verificar los tokens a medida que se presentan.
Estamos almacenando la clave en una
key
variable de nivel de clase que reutilizaremos en el método de configuración a continuación.
TokenAuthOptions
es una clase simple que contiene la identidad de firma, la audiencia y el emisor que necesitaremos en TokenController para crear nuestras claves.
// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();
// Create the key, and a set of token options to record signing credentials
// using that key, along with the other parameters we will need in the
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};
// Save the token options into an instance so they''re accessible to the
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);
// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
También hemos establecido una política de autorización que nos permite usar
[Authorize("Bearer")]
en los puntos finales y las clases que deseamos proteger.
Startup.cs - Configurar
Aquí, necesitamos configurar JwtBearerAuthentication:
app.UseJwtBearerAuthentication(new JwtBearerOptions {
TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = key,
ValidAudience = tokenOptions.Audience,
ValidIssuer = tokenOptions.Issuer,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew - i.e.
// provides a tolerance on the token expiry time
// when validating the lifetime. As we''re creating the tokens
// locally and validating them on the same machines which
// should have synchronised time, this can be set to zero.
// Where external tokens are used, some leeway here could be
// useful.
ClockSkew = TimeSpan.FromMinutes(0)
}
});
TokenController
En el controlador de token, debe tener un método para generar claves firmadas utilizando la clave que se cargó en Startup.cs. Hemos registrado una instancia de TokenAuthOptions en Startup, por lo que debemos inyectarla en el constructor de TokenController:
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenAuthOptions tokenOptions;
public TokenController(TokenAuthOptions tokenOptions)
{
this.tokenOptions = tokenOptions;
}
...
Luego, deberá generar el token en su controlador para el punto final de inicio de sesión, en mi ejemplo, estoy tomando un nombre de usuario y contraseña y validando los que usan una declaración if, pero lo clave que debe hacer es crear o cargar un reclamo basada en identidad y generar el token para eso:
public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}
/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
// Obviously, at this point you need to validate the username and password against whatever system you wish.
if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
{
DateTime? expires = DateTime.UtcNow.AddMinutes(2);
var token = GetToken(req.username, expires);
return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
}
return new { authenticated = false };
}
private string GetToken(string user, DateTime? expires)
{
var handler = new JwtSecurityTokenHandler();
// Here, you should create or look up an identity for the user which is being authenticated.
// For now, just creating a simple generic identity.
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });
var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
Issuer = tokenOptions.Issuer,
Audience = tokenOptions.Audience,
SigningCredentials = tokenOptions.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}
Y eso debería ser todo.
Simplemente agregue
[Authorize("Bearer")]
a cualquier método o clase que desee proteger, y debería obtener un error si intenta acceder a él sin un token presente.
Si desea devolver un error 401 en lugar de 500, deberá registrar un controlador de excepciones personalizado
como lo hice en mi ejemplo aquí
.