Ich habe diese Frage beantwortet: So sichern Sie eine ASP.NET-Web-API vor 4 Jahren mit HMAC.
Jetzt haben sich viele Dinge in Bezug auf die Sicherheit geändert, insbesondere JWT wird immer beliebter. Hier werde ich versuchen zu erklären, wie man JWT so einfach und grundlegend wie möglich verwendet, damit wir uns nicht aus dem Dschungel von OWIN, Oauth2, ASP.NET Identity ... :) verlieren.
Wenn Sie das JWT-Token nicht kennen, müssen Sie sich Folgendes genauer ansehen:
https://tools.ietf.org/html/rfc7519
Grundsätzlich sieht ein JWT-Token folgendermaßen aus:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Beispiel:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
Ein JWT-Token besteht aus drei Abschnitten:
- Header: JSON-Format, das in Base64 codiert ist
- Ansprüche: JSON-Format, das in Base64 codiert ist.
- Signatur: Erstellt und signiert basierend auf Header und Ansprüchen, die in Base64 codiert sind.
Wenn Sie die Website jwt.io mit dem obigen Token verwenden, können Sie das Token dekodieren und wie folgt anzeigen :
Technisch gesehen verwendet JWT eine Signatur, die von Headern und Ansprüchen mit dem in den Headern angegebenen Sicherheitsalgorithmus signiert ist (Beispiel: HMACSHA256). Daher muss JWT über HTTPs übertragen werden, wenn Sie vertrauliche Informationen in Ansprüchen speichern.
Um die JWT-Authentifizierung verwenden zu können, benötigen Sie keine OWIN-Middleware, wenn Sie über ein älteres Web-API-System verfügen. Das einfache Konzept besteht darin, ein JWT-Token bereitzustellen und das Token zu validieren, wenn die Anforderung eingeht. Das ist es.
Zurück zur Demo, um JWT-Token leicht zu halten, speichere ich nur username
und expiration time
in JWT. Auf diese Weise müssen Sie jedoch eine neue lokale Identität (Principal) neu erstellen, um weitere Informationen hinzuzufügen, z. B.: Rollen .., wenn Sie eine Rollenautorisierung durchführen möchten. Wenn Sie jedoch weitere Informationen zu JWT hinzufügen möchten, liegt es an Ihnen: Es ist sehr flexibel.
Anstatt die OWIN-Middleware zu verwenden, können Sie einfach einen JWT-Token-Endpunkt bereitstellen, indem Sie die Aktion des Controllers verwenden:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
Dies ist eine naive Handlung; In der Produktion sollten Sie eine POST-Anforderung oder einen Basisauthentifizierungsendpunkt verwenden, um das JWT-Token bereitzustellen.
Wie wird das Token basierend auf generiert username
?
Sie können das System.IdentityModel.Tokens.Jwt
von Microsoft aufgerufene NuGet-Paket verwenden , um das Token zu generieren, oder sogar ein anderes Paket, wenn Sie möchten. In der Demo verwende ich HMACSHA256
mit SymmetricKey
:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
Der Endpunkt für die Bereitstellung des JWT-Tokens ist fertig. Wie kann nun die JWT validiert werden, wenn die Anfrage eingeht? In der Demo habe ich gebaut,
JwtAuthenticationAttribute
welche erbt IAuthenticationFilter
(mehr Details zum Authentifizierungsfilter hier ).
Mit diesem Attribut können Sie jede Aktion authentifizieren: Sie müssen dieses Attribut nur für diese Aktion festlegen.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
Sie können auch OWIN Middleware oder DelegateHander verwenden, wenn Sie alle eingehenden Anforderungen für Ihre WebAPI überprüfen möchten (nicht spezifisch für Controller oder Aktion).
Nachfolgend finden Sie die Kernmethode des Authentifizierungsfilters:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
Der Workflow besteht darin, mithilfe der JWT-Bibliothek (NuGet-Paket oben) das JWT-Token zu validieren und dann zurückzukehren ClaimsPrincipal
. Sie können weitere Überprüfungen durchführen, z. B. prüfen, ob Benutzer auf Ihrem System vorhanden sind, und bei Bedarf weitere benutzerdefinierte Überprüfungen hinzufügen. Der Code zum Validieren des JWT-Tokens und zum Zurückerhalten des Principals:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
Wenn das JWT-Token validiert ist und der Principal zurückgegeben wird, sollten Sie eine neue lokale Identität erstellen und weitere Informationen einfügen, um die Rollenautorisierung zu überprüfen.
Denken Sie daran, config.Filters.Add(new AuthorizeAttribute());
im globalen Bereich eine Standardautorisierung hinzuzufügen , um anonyme Anforderungen an Ihre Ressourcen zu vermeiden.
Sie können Postman verwenden, um die Demo zu testen:
Token anfordern (naiv wie oben erwähnt, nur zur Demo):
GET http://localhost:{port}/api/token?username=cuong&password=1
Fügen Sie das JWT-Token für eine autorisierte Anforderung in den Header ein. Beispiel:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
Die Demo finden Sie hier: https://github.com/cuongle/WebApi.Jwt