Odpowiedziałem na to pytanie: Jak zabezpieczyć interfejs API sieci Web ASP.NET 4 lata temu za pomocą HMAC.
Teraz wiele rzeczy zmieniło się pod względem bezpieczeństwa, szczególnie JWT zyskuje na popularności. W tym miejscu postaram się wyjaśnić, jak korzystać z JWT w najprostszy i podstawowy sposób, w jaki mogę, abyśmy nie zgubili się w dżungli OWIN, Oauth2, ASP.NET Identity ... :).
Jeśli nie znasz tokena JWT, musisz rzucić okiem na:
https://tools.ietf.org/html/rfc7519
Zasadniczo token JWT wygląda następująco:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Przykład:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
Token JWT ma trzy sekcje:
- Nagłówek: format JSON zakodowany w Base64
- Roszczenia: Format JSON zakodowany w Base64.
- Podpis: utworzony i podpisany na podstawie nagłówka i oświadczeń zakodowanych w Base64.
Jeśli używasz witryny jwt.io z powyższym tokenem, możesz go zdekodować i zobaczyć jak poniżej:

Technicznie JWT używa podpisu podpisanego z nagłówków i oświadczeń za pomocą algorytmu bezpieczeństwa określonego w nagłówkach (przykład: HMACSHA256). Dlatego wymagane jest przesyłanie JWT przez HTTPs, jeśli przechowujesz poufne informacje w roszczeniach.
Teraz, aby korzystać z uwierzytelniania JWT, tak naprawdę nie potrzebujesz oprogramowania pośredniego OWIN, jeśli masz starszy system Web Api. Prosta koncepcja polega na tym, jak dostarczyć token JWT i jak sprawdzić poprawność tokena, gdy nadejdzie żądanie. Otóż to.
Powrót na demo, aby zachować JWT tokena lekka, tylko przechowywanie usernamei expiration timew JWT. Ale w ten sposób musisz odbudować nową tożsamość lokalną (podmiot główny), aby dodać więcej informacji, takich jak: role .. jeśli chcesz wykonać autoryzację roli. Ale jeśli chcesz dodać więcej informacji do JWT, to zależy od ciebie: jest bardzo elastyczny.
Zamiast używać oprogramowania pośredniego OWIN, możesz po prostu podać punkt końcowy tokena JWT, używając akcji z kontrolera:
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;
}
}
To naiwne działanie; w środowisku produkcyjnym należy użyć żądania POST lub punktu końcowego podstawowego uwierzytelniania, aby dostarczyć token JWT.
Jak wygenerować token na podstawie username?
Możesz użyć pakietu NuGet wywoływanego System.IdentityModel.Tokens.Jwtod Microsoft do wygenerowania tokena, a nawet innego pakietu, jeśli chcesz. W wersji demo używam HMACSHA256z 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;
}
Punkt końcowy do dostarczenia tokenu JWT został zakończony. Jak sprawdzić poprawność JWT, gdy nadejdzie żądanie? W wersji demonstracyjnej, którą zbudowałem,
JwtAuthenticationAttributektóra dziedziczy IAuthenticationFilter(więcej szczegółów na temat filtra uwierzytelniania tutaj ).
Za pomocą tego atrybutu możesz uwierzytelnić dowolne działanie: wystarczy umieścić ten atrybut na tym działaniu.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
Możesz również użyć oprogramowania pośredniego OWIN lub DelegateHander, jeśli chcesz zweryfikować wszystkie przychodzące żądania dotyczące interfejsu WebAPI (nie dotyczy kontrolera ani działania)
Poniżej znajduje się podstawowa metoda z filtru uwierzytelniania:
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);
}
Przepływ pracy polega na użyciu biblioteki JWT (pakiet NuGet powyżej) do sprawdzenia poprawności tokena JWT, a następnie powrotu ClaimsPrincipal. Możesz przeprowadzić większą weryfikację, np. Sprawdzić, czy użytkownik istnieje w systemie i dodać inne niestandardowe weryfikacje, jeśli chcesz. Kod sprawdzania poprawności tokenu JWT i odzyskania kwoty głównej:
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;
}
}
Jeśli token JWT jest sprawdzony, a nazwa główna jest zwracana, należy zbudować nową tożsamość lokalną i umieścić w niej więcej informacji, aby sprawdzić autoryzację roli.
Pamiętaj, aby dodać config.Filters.Add(new AuthorizeAttribute());(domyślna autoryzacja) w zakresie globalnym, aby zapobiec anonimowym żądaniom do Twoich zasobów.
Możesz użyć Listonosza do przetestowania wersji demo:
Żeton żądania (naiwny, jak wspomniałem powyżej, tylko dla wersji demonstracyjnej):
GET http://localhost:{port}/api/token?username=cuong&password=1
Umieść token JWT w nagłówku autoryzowanego żądania, przykład:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
Demo znajduje się tutaj: https://github.com/cuongle/WebApi.Jwt