Декодирование и проверка токена 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