Декодирование и проверка токена JWT с использованием System.IdentityModel.Tokens.Jwt
Я использовал библиотеку JWT для декодирования Json Web Token и хотел бы перейти на официальную реализацию Microsoft JWT, System.IdentityModel.Tokens.Jwt.
Документация очень скудная, поэтому мне трудно понять, как выполнить то, что я делал с библиотекой JWT. В библиотеке JWT существует метод Decode, который использует кодировку JWT с кодировкой base64 и превращает ее в JSON, который затем можно десериализовать. Я хотел бы сделать что-то подобное, используя System.IdentityModel.Tokens.Jwt, но после достаточного количества рытья не могу понять, как это сделать.
Для чего это стоит, я читаю токен JWT из файла cookie для использования с системой идентификации Google.
Любая помощь будет оценена.
Ответы
Ответ 1
Внутри пакета есть класс под названием JwtSecurityTokenHandler
, который происходит от System.IdentityModel.Tokens.SecurityTokenHandler
. В WIF это основной класс для десериализации и сериализации маркеров безопасности.
Класс имеет метод ReadToken(String)
, который возьмет вашу строку JWT, закодированную в base64, и вернет SecurityToken
, который представляет JWT.
SecurityTokenHandler
также имеет метод ValidateToken(SecurityToken)
, который принимает ваш SecurityToken
и создает ReadOnlyCollection<ClaimsIdentity>
. Обычно для JWT это будет содержать единственный объект ClaimsIdentity
, который имеет набор претензий, представляющих свойства исходного JWT.
JwtSecurityTokenHandler
определяет некоторые дополнительные перегрузки для ValidateToken
, в частности, имеет перегрузку ClaimsPrincipal ValidateToken(JwtSecurityToken, TokenValidationParameters)
. Аргумент TokenValidationParameters
позволяет указать сертификат подписи маркера (в виде списка X509SecurityTokens
). Он также имеет перегрузку, которая принимает JWT как string
, а не SecurityToken
.
Код для этого довольно сложный, но его можно найти в коде Global.asax.cx(TokenValidationHandler
class) в примере разработчика, называемом "ADAL - Native App to REST service" - аутентификация с помощью ACS через диалог браузера ", расположенный в
http://code.msdn.microsoft.com/AAL-Native-App-to-REST-de57f2cc
В качестве альтернативы класс JwtSecurityToken
имеет дополнительные методы, которые не относятся к базовому классу SecurityToken
, например к свойству Claims
, которое получает содержащиеся в нем претензии без прохождения через коллекцию ClaimsIdentity
. Он также имеет свойство Payload
, которое возвращает объект JwtPayload
, который позволяет получить исходный JSON токена. Это зависит от вашего сценария, который наиболее подходит ему.
Общая (то есть не специфичная для JWT) документация для класса SecurityTokenHandler
находится в
http://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.securitytokenhandler.aspx
В зависимости от вашего приложения вы можете настроить обработчик JWT в WIF-конвейер точно так же, как и любой другой обработчик.
Существует 3 образца, используемых в разных типах приложений в
http://code.msdn.microsoft.com/site/search?f%5B0%5D.Type=SearchText&f%5B0%5D.Value=aal&f%5B1%5D.Type=User&f%5B1%5D.Value=Azure%20AD%20Developer%20Experience%20Team&f%5B1%5D.Text=Azure%20AD%20Developer%20Experience%20Team
Вероятно, один из них будет соответствовать вашим потребностям или, по крайней мере, будет адаптироваться к ним.
Ответ 2
Мне просто интересно, почему использовать некоторые библиотеки для декодирования и проверки токена JWT вообще.
Обозначенный токен JWT может быть создан с использованием после псевдокода
var headers = base64URLencode(myHeaders);
var claims = base64URLencode(myClaims);
var payload = header + "." + claims;
var signature = base64URLencode(HMACSHA256(payload, secret));
var encodedJWT = payload + "." + signature;
Это очень легко обойтись без какой-либо конкретной библиотеки. Используя следующий код:
using System;
using System.Text;
using System.Security.Cryptography;
public class Program
{
// More info: https://stormpath.com/blog/jwt-the-right-way/
public static void Main()
{
var header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
var claims = "{\"sub\":\"1047986\",\"email\":\"[email protected]\",\"given_name\":\"John\",\"family_name\":\"Doe\",\"primarysid\":\"b521a2af99bfdc65e04010ac1d046ff5\",\"iss\":\"http://example.com\",\"aud\":\"myapp\",\"exp\":1460555281,\"nbf\":1457963281}";
var b64header = Convert.ToBase64String(Encoding.UTF8.GetBytes(header))
.Replace('+', '-')
.Replace('/', '_')
.Replace("=", "");
var b64claims = Convert.ToBase64String(Encoding.UTF8.GetBytes(claims))
.Replace('+', '-')
.Replace('/', '_')
.Replace("=", "");
var payload = b64header + "." + b64claims;
Console.WriteLine("JWT without sig: " + payload);
byte[] key = Convert.FromBase64String("mPorwQB8kMDNQeeYO35KOrMMFn6rFVmbIohBphJPnp4=");
byte[] message = Encoding.UTF8.GetBytes(payload);
string sig = Convert.ToBase64String(HashHMAC(key, message))
.Replace('+', '-')
.Replace('/', '_')
.Replace("=", "");
Console.WriteLine("JWT with signature: " + payload + "." + sig);
}
private static byte[] HashHMAC(byte[] key, byte[] message)
{
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
}
Декодирование токена - это обратная версия вышеприведенного кода. Чтобы проверить подпись, вам понадобится то же самое и сравните сигнатурную часть с вычисленной сигнатурой.
UPDATE: для тех, кто борется, как сделать кодировку/декодирование base64 urlsafe, см. другой вопрос SO, а также wiki и RFC