Lavorando dalla favolosa risposta di Matt Dekrey , ho creato un esempio pienamente funzionante di autenticazione basata su token, lavorando con ASP.NET Core (1.0.1). Puoi trovare il codice completo in questo repository su GitHub (rami alternativi per 1.0.0-rc1 , beta8 , beta7 ), ma in breve, i passaggi importanti sono:
Genera una chiave per la tua applicazione
Nel mio esempio, creo una chiave casuale ogni volta che viene avviata l'app, dovrai generarne una e memorizzarla da qualche parte e fornirla alla tua applicazione. Vedi questo file per come sto generando una chiave casuale e come potresti importarla da un file .json . Come suggerito nei commenti di @kspearrin, l' API per la protezione dei dati sembra un candidato ideale per gestire le chiavi "correttamente", ma non ho ancora capito se è ancora possibile. Invia una richiesta pull se la risolvi!
Startup.cs - ConfigureServices
Qui, dobbiamo caricare una chiave privata per i nostri token con cui firmare, che useremo anche per verificare i token man mano che vengono presentati. Stiamo memorizzando la chiave in una variabile a livello di classe key
che riutilizzeremo nel metodo Configura di seguito. TokenAuthOptions è una classe semplice che contiene l'identità della firma, il pubblico e l'emittente di cui avremo bisogno in TokenController per creare le nostre chiavi.
// 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());
});
Abbiamo anche impostato una politica di autorizzazione per consentirci di utilizzare [Authorize("Bearer")]
gli endpoint e le classi che desideriamo proteggere.
Startup.cs: configura
Qui, dobbiamo configurare 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
Nel controller token, è necessario disporre di un metodo per generare chiavi firmate utilizzando la chiave caricata in Startup.cs. Abbiamo registrato un'istanza TokenAuthOptions in Startup, quindi dobbiamo iniettarla nel costruttore per TokenController:
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenAuthOptions tokenOptions;
public TokenController(TokenAuthOptions tokenOptions)
{
this.tokenOptions = tokenOptions;
}
...
Quindi dovrai generare il token nel tuo gestore per l'endpoint di accesso, nel mio esempio sto prendendo un nome utente e una password e convalidando quelli usando un'istruzione if, ma la cosa chiave che devi fare è creare o caricare un reclamo identità basata e generare il token per quello:
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);
}
E questo dovrebbe essere tutto. Basta aggiungere [Authorize("Bearer")]
a qualsiasi metodo o classe che si desidera proteggere e si dovrebbe ricevere un errore se si tenta di accedervi senza un token presente. Se vuoi restituire un 401 invece di un errore 500, dovrai registrare un gestore di eccezioni personalizzato come nel mio esempio qui .