Как правильно подготовить запрос 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));
}
}
Ответы
Ответ 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());
}
}
}