Как правильно подготовить SAML-запрос "HTTP Redirect Binding", используя C#
Мне нужно создать транзакцию аутентификации SAML 2.0, инициированную SP, используя метод HTTP Redirect Binding. Оказывается, это довольно просто. Просто получите URI IdP и объедините один параметр строки запроса 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, который должен реализовывать стандартный отраслевой алгоритм 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());
}
}
}