Подтвердить что ты не робот

Как правильно подготовить запрос SAML для перенаправления HTTP-адресов с использованием С#

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

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

  • Построить строку SAML
  • Сжатие этой строки
  • Base64 кодирует строку
  • 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, который должен реализовывать стандартный алгоритм дефлята, поэтому, возможно, здесь есть некоторые настройки. я ошибаетесь?

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

Таким образом, возникает вопрос: знаете ли вы, как преобразовать строку SAML в правильно дефлированный и закодированный запрос-параметр SAMLRequest?

Спасибо

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

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

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

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, показанный в верхней части этого вопроса.

Декодировать SAMLResponse - Рабочий код

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));
    }
}
4b9b3361

Ответ 1

Я только что запустил следующий код с вашим примером 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 roundtrip выполняется правильно. Ошибка должна произойти позже. Может быть, URLEncoder уничтожает их? Не могли бы вы попробовать аналогичный тест urlencode/decode? Кроме того, проверьте, как долго результат. Возможно, результирующий URL-адрес усечен из-за его длины.

(редактирование: я добавил 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. Я не имею ни малейшего представления, почему SharpZip Deflate не принимается сайтом "отладчик". Нельзя отрицать, что сжатие работает, как это удается распаковать данные правильно.. он просто должен быть некоторые различия в алгоритмах, но я не могу сказать, в чем разница между этим Deflate и что Deflate, д о.

Ответ 2

Вопрос наверху содержит раздел "Декодирование 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());
    }
  }
}