tutorial - ¿Cómo preparo correctamente una solicitud SAML de ''Enlace de redireccionamiento HTTP'' usando C#?
single sign on saml2 (2)
Acabo de ejecutar el siguiente código con su ejemplo 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;
La variable de prueba es true
. Esto significa que el viaje de ida y vuelta zip / base64 / unbase64 / unzip funciona correctamente. El error debe ocurrir más tarde. Tal vez el codificador URLE los destruya? ¿Podrías probar con urlencode / decode test similar? Además, comprueba cuánto tiempo es el resultado. Es posible que la URL resultante esté truncada debido a su longitud.
(Editar: He agregado un StreamReader en lugar de leer en arreglos. Anteriormente, mi muestra usaba bytes. Duración para preparar el búfer y eso podría dañar la prueba. Ahora la lectura usa solo la información del flujo comprimido)
editar:
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;
este código produce una variable middle
, que una vez que es UrlEncoded, pasa a través del depurador correctamente. DeflateStream
proviene del espacio de nombres System.IO.Compression
estándar de .Net. No tengo la menor idea de por qué el sitio de "depurador" no acepta el Deflate de SharpZip. Es innegable que la compresión funciona, ya que logra descomprimir los datos correctamente ... solo tiene que haber alguna diferencia en los algoritmos, pero no puedo decir cuál es la diferencia entre este desinflado y ese desinflado, d.
Necesito crear una transacción de autenticación SAML 2.0 iniciada por SP utilizando el método de enlace de redireccionamiento HTTP. Resulta que esto es bastante fácil. Solo obtenga el URI de IdP y concatene una sola cadena de consulta param SAMLRequest
. El parámetro es un bloque codificado de xml que describe la solicitud SAML. Hasta ahora tan bueno.
El problema viene al convertir el SAML en el parámetro de cadena de consulta. Creo que este proceso de preparación debe ser:
- Construye una cadena SAML
- Comprimir esta cadena
- Base64 codifica la cadena
- Urlcifre la cadena.
La solicitud 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>
El código
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);
}
}
Sospecho que la compresión es de alguna manera la culpa. Estoy usando la clase DeflaterOutputStream
de SharpZipLib que se supone que implementa un algoritmo de desinflado estándar de la industria, por lo que quizás haya algunas configuraciones aquí.
La salida codificada se puede probar utilizando este depurador SAML2.0 (es una herramienta de conversión en línea útil). Cuando decodifico mi salida usando esta herramienta, sale como una tontería.
Por lo tanto, la pregunta es: ¿Sabe cómo convertir una cadena SAML en el parámetro de consulta SAMLRequest correctamente desinflado y codificado?
Gracias
EDITAR 1
La respuesta aceptada a continuación da la respuesta al problema. Aquí está el código final corregido por todos los comentarios y respuestas posteriores.
Codificar SAMLRequest - Código de trabajo
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);
}
}
La variable SAMLRequest
contiene la SAML que se muestra en la parte superior de esta pregunta.
Decodificar SAMLResponse - Código de trabajo
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));
}
}
La pregunta en la parte superior contiene una sección "Decodificar SAMLResponse - Código de trabajo", pero ese código parecía roto. Después de probar algunas cosas, descubrí que estaba intentando leer y escribir en la misma secuencia al mismo tiempo. Lo modifiqué separando los flujos de lectura y escritura y aquí está mi solución (estoy proporcionando la sección de solicitud para mayor comodidad y claridad):
Codificar solicitud de autenticación 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);
}
}
Decodificar respuesta de autenticación 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());
}
}
}