Пример использования JSON Web Tokens для интеграции Directum RX с внешним сервисом

18 0

Что такое JSON Web Tokens?

Простыми словами, JSON Web Tokens (JWT)— это строка в следующем формате <header>.<payload>.<signature>.

Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях.

Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.

Подробно можно почитать на Wiki и Habr.

Отправка запросов из RX

Пригодится, если внешний сервис требует (поддерживает) 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

Из «коробки» 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 для интеграции разных систем?

Какие ещё методы авторизации Вы использовали на проектах?

Пишите в комментариях!

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий