Как правильно подготовить SAML-запрос "HTTP Redirect Binding", используя C#

Мне нужно создать транзакцию аутентификации SAML 2.0, инициированную SP, используя метод HTTP Redirect Binding. Оказывается, это довольно просто. Просто получите URI IdP и объедините один параметр строки запроса SAMLRequest, Параметр является закодированным блоком xml, который описывает запрос SAML. Все идет нормально.

Проблема возникает при преобразовании SAML в параметр строки запроса. Я считаю, что этот процесс подготовки должен быть:

  1. Создайте строку SAML
  2. Сожмите эту строку
  3. Base64 кодирует строку
  4. UrlEncode строка.

Запрос SAML

<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="{0}"
    Version="2.0"
    AssertionConsumerServiceIndex="0"
    AttributeConsumingServiceIndex="0">
    <saml:Issuer>URN:xx-xx-xx</saml:Issuer>
    <samlp:NameIDPolicy
        AllowCreate="true"
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>

Код

private string GetSAMLHttpRedirectUri(string idpUri)
{
    var saml = string.Format(SAMLRequest, Guid.NewGuid());
    var bytes = Encoding.UTF8.GetBytes(saml);
    using (var output = new MemoryStream())
    {
        using (var zip = new DeflaterOutputStream(output))
        {
            zip.Write(bytes, 0, bytes.Length);
        }
        var base64 = Convert.ToBase64String(output.ToArray());
        var urlEncode = HttpUtility.UrlEncode(base64);
        return string.Concat(idpUri, "?SAMLRequest=", urlEncode);
    }
}

Я подозреваю, что компрессия как-то виновата. Я использую DeflaterOutputStream класс от SharpZipLib, который должен реализовывать стандартный отраслевой алгоритм deflate, так что, возможно, здесь есть некоторые настройки, которые я ошибаюсь?

Закодированный вывод можно протестировать с помощью этого отладчика SAML2.0 (его полезный инструмент онлайн-конвертации). Когда я декодирую свой вывод, используя этот инструмент, это звучит как чепуха.

Поэтому возникает вопрос: знаете ли вы, как преобразовать строку SAML в правильно скомпилированный и закодированный параметр запроса SAML Request?

Спасибо

РЕДАКТИРОВАТЬ 1

Принятый ответ ниже дает ответ на проблему. Вот окончательный код, исправленный всеми последующими комментариями и ответами.

Кодировать SAML Request - Рабочий код

private string GenerateSAMLRequestParam()
{
    var saml = string.Format(SAMLRequest, Guid.NewGuid());
    var bytes = Encoding.UTF8.GetBytes(saml);
    using (var output = new MemoryStream())
    {
        using (var zip = new DeflateStream(output, CompressionMode.Compress))
        {
            zip.Write(bytes, 0, bytes.Length);
        }
        var base64 = Convert.ToBase64String(output.ToArray());
        return HttpUtility.UrlEncode(base64);
    }
}

SAMLRequest переменная содержит SAML, показанный в верхней части этого вопроса.

Расшифровать SAML Response - рабочий код

private string DecodeSAMLResponse(string response)
{
    var utf8 = Encoding.UTF8;
    var bytes = utf8.GetBytes(response);
    using (var output = new MemoryStream())
    {
        using (new DeflateStream(output, CompressionMode.Decompress))
        {
            output.Write(bytes, 0, bytes.Length);
        }
        var base64 = utf8.GetString(output.ToArray());
        return utf8.GetString(Convert.FromBase64String(base64));
    }
}

2 ответа

Решение

Я только что запустил следующий код с вашим примером SAML:

        var saml = string.Format(sample, Guid.NewGuid());
        var bytes = Encoding.UTF8.GetBytes(saml);

        string middle;
        using (var output = new MemoryStream())
        {
            using (var zip = new DeflaterOutputStream(output))
                zip.Write(bytes, 0, bytes.Length);

            middle = Convert.ToBase64String(output.ToArray());
        }

        string decoded;
        using (var input = new MemoryStream(Convert.FromBase64String(middle)))
        using (var unzip = new InflaterInputStream(input))
        using (var reader = new StreamReader(unzip, Encoding.UTF8))
            decoded = reader.ReadToEnd();

        bool test = decoded == saml;

Тестовая переменная true, Это означает, что круговая передача zip/base64/unbase64/unzip работает правильно. Ошибка должна появиться позже. Может быть, URLEncoder уничтожает их? Не могли бы вы попробовать аналогичный тест urlencode/decode? Также проверьте, как долго длится результат. Возможно, что полученный URL-адрес будет обрезан из-за его длины.

(edit: я добавил StreamReader вместо чтения в массивы. Ранее мой пример использовал bytes.Length для подготовки буфера, и это могло повредить тест. Теперь чтение использует только информацию из сжатого потока)

редактировать:

        var saml = string.Format(sample, Guid.NewGuid());
        var bytes = Encoding.UTF8.GetBytes(saml);

        string middle;
        using (var output = new MemoryStream())
        {
            using (var zip = new DeflateStream(output, CompressionMode.Compress))
                zip.Write(bytes, 0, bytes.Length);

            middle = Convert.ToBase64String(output.ToArray());
        }

        // MIDDLE is the thing that should be now UrlEncode'd

        string decoded;
        using (var input = new MemoryStream(Convert.FromBase64String(middle)))
        using (var unzip = new DeflateStream(input, CompressionMode.Decompress))
        using (var reader = new StreamReader(unzip, Encoding.UTF8))
            decoded = reader.ReadToEnd();

        bool test = decoded == saml;

этот код производит middle переменная, которая когда-то была UrlEncoded, проходит через отладчик должным образом. DeflateStream исходит из стандартного.Net System.IO.Compression Пространство имен. Я не имею ни малейшего представления, почему сайт Defarpger не принимает Deflate SharpZip. Нельзя отрицать, что сжатие работает, как это удается распаковать данные правильно.. он просто должен быть некоторые различия в алгоритмах, но я не могу сказать, в чем разница между этим Deflate и что Deflate, d'о.

Вопрос вверху содержит раздел "Декодирование SAMLResponse - рабочий код", но этот код выглядел неработающим. Попробовав несколько вещей, я обнаружил, что он пытается читать и записывать в один и тот же поток одновременно. Я переработал его, разделив потоки чтения и записи, и вот мое решение (для удобства и ясности я предоставляю раздел запроса):

Кодировать запрос аутентификации SAML:

public static string EncodeSamlAuthnRequest(this string authnRequest) {
    var bytes = Encoding.UTF8.GetBytes(authnRequest);
    using (var output = new MemoryStream()) {
      using (var zip = new DeflateStream(output, CompressionMode.Compress)) {
        zip.Write(bytes, 0, bytes.Length);
      }
      var base64 = Convert.ToBase64String(output.ToArray());
      return HttpUtility.UrlEncode(base64);
    }
  }

Расшифруйте ответ аутентификации SAML:

public static string DecodeSamlAuthnRequest(this string encodedAuthnRequest) {
  var utf8 = Encoding.UTF8;
  var bytes = Convert.FromBase64String(HttpUtility.UrlDecode(encodedAuthnRequest));
  using (var output = new MemoryStream()) {
    using (var input = new MemoryStream(bytes)) {
      using (var unzip = new DeflateStream(input, CompressionMode.Decompress)) {
        unzip.CopyTo(output, bytes.Length);
        unzip.Close();
      }
      return utf8.GetString(output.ToArray());
    }
  }
}
Другие вопросы по тегам