Простыми словами, JSON Web Tokens (JWT)— это строка в следующем формате <header>.<payload>.<signature>.
Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях.
Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.
Подробно можно почитать на Wiki и Habr.
Пригодится, если внешний сервис требует (поддерживает) JWT авторизацию.
1. Первым делом скачиваем библиотеку, которая позволит работать с JWT без лишних заморочек. На сайте https://jwt.io/ можно выбрать нужную версию, я использовал https://github.com/jwt-dotnet/jwt. Там же есть подробное описание с примерами использования.
2. Подключаем библиотеку к нашему модулю.
3. Вместо тысячи слов, пример подготовки и отправки запроса:
using JWT;
//…
// Сформировать полезную нагрузку.
Dictionary<string, object> payload = new Dictionary<string, object>(3);
payload.Add("userId", "123456");
// Время жизни токена.
payload.Add("exp", DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds());
payload.Add("otherInfo", "someInfo");
var secret = "<Секретный ключ для подписания>";
JWT.Algorithms.IJwtAlgorithm algorithm = new JWT.Algorithms.HMACSHA256Algorithm();
JWT.IJsonSerializer serializer = new JWT.Serializers.JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var jwtToken = encoder.Encode(payload, secret); // Получим строку вида: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYiLCJleHAiOiIxMi4xMi4yMDIwIiwib3RoZXJJbmZvIjoic29tZUluZm8ifQ.tXU_5KBl2B-qsbA4VpjH4-KuBfqszyg4MrImPzfmG6s
// Подготовить запрос к внешнему сервису.
var request = HttpWebRequest.Create("<serviceUrl>");
request.Method = "GET";
request.Headers.Add("Authorization", "Bearer " + jwtToken);
var response = (HttpWebResponse)request.GetResponse();
using (var stream = response.GetResponseStream())
{
// Обработка ответа...
}
Запрос отправлен. Не забудьте добавить обработку ошибок на случай непредвиденных ситуаций.
Из «коробки» WebApi RX требует передачи логина и пароля пользователя в каждом запросе (Base Auth).
Рассмотрим пример реализации авторизации с помощью JWT.
В папке «App_Start» можно найти файл «BasicAuthenticationAttribute.cs».
Добавим рядом новый файл «JWTAuthAttribute.cs» следующего содержания:
using System;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Results;
using JWT;
namespace Sungero.IntegrationWebApiService
{
public class JWTAuthAttribute : Attribute, IAuthenticationFilter
{
public bool AllowMultiple { get; }
private const string AuthorizationScheme = "Bearer";
private string jwtSecret = "<Ключ для проверки подписи>";
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
try
{
var authHeader = context.Request.Headers.Authorization;
if (!authHeader.Scheme.Equals(AuthorizationScheme))
{
context.ErrorResult = new UnauthorizedResult(new[] { AuthenticationHeaderValue.Parse($"Authentication failed. Invalid authorization scheme = \"{authHeader.Scheme}\".") }, context.Request);
return Task.CompletedTask;
}
var jwtToken = authHeader.Parameter;
try
{
IJsonSerializer serializer = new JWT.Serializers.JsonNetSerializer();
var provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
JWT.Algorithms.IJwtAlgorithm algorithm = new JWT.Algorithms.HMACSHA256Algorithm();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
var json = decoder.Decode(jwtToken, jwtSecret, verify: true);
}
catch (JWT.Exceptions.TokenExpiredException)
{
context.ErrorResult = new UnauthorizedResult(new[] { AuthenticationHeaderValue.Parse("Authentication failed. Token has expired.") }, context.Request);
return Task.CompletedTask;
}
catch (JWT.Exceptions.SignatureVerificationException)
{
context.ErrorResult = new UnauthorizedResult(new[] { AuthenticationHeaderValue.Parse("Authentication failed. Token has invalid signature.") }, context.Request);
return Task.CompletedTask;
}
}
catch (Exception ex)
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
return Task.CompletedTask;
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(context.Result);
}
}
}
Осталось добавить новый атрибут в описание метода в контролёре.
/// <summary>
/// Получение сообщений из внешней системы.
/// </summary>
[HttpPost]
[JWTAuth]
[ODataRoute("NewMethodName")]
public IHttpActionResult NewMethodName()
{
//…
}
Теперь все запросы к методу «NewMethodName» будут требовать передачи валидного JWT, иначе вернётся ошибка авторизации.
При необходимости, можно добавить проверку содержания полезной нагрузки токена.
Вопрос о плюсах, минусах и тонкостях применения JWT оставлю открытым.
Был ли у Вас опыт применения JWT для интеграции разных систем?
Какие ещё методы авторизации Вы использовали на проектах?
Пишите в комментариях!
Авторизуйтесь, чтобы написать комментарий