如何使用C#正确准备“HTTP重定向绑定”SAML请求

时间:2012-08-23 11:19:34

标签: c# saml-2.0 sharpziplib

我需要使用HTTP重定向绑定方法创建SP启动的SAML 2.0身份验证事务。事实证明这很容易。只需获取IdP URI并连接单个查询字符串参数SAMLRequest。 param是xml的编码块,用于描述SAML请求。到目前为止一切都很好。

将SAML转换为查询字符串参数时出现问题。我相信这个准备过程应该是:

  1. 构建SAML字符串
  2. 压缩此字符串
  3. Base64编码字符串
  4. UrlEncode the string。
  5. 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);
        }
    }
    

    我怀疑压缩是某种原因。我正在使用来自SharpZipLibDeflaterOutputStream类,它应该实现行业标准的deflate算法,所以也许这里有一些设置我错了?

    可以使用此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));
        }
    }
    

2 个答案:

答案 0 :(得分:13)

我刚刚使用您的示例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可能会被截断。

(编辑:我添加了一个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之间有什么区别,d'oh。

答案 1 :(得分:9)

顶部的问题包含“解码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());
    }
  }
}