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

Posted

技术标签:

【中文标题】如何使用 C# 正确准备“HTTP 重定向绑定”SAML 请求【英文标题】:How do I correctly prepare an 'HTTP Redirect Binding' SAML Request using C# 【发布时间】:2012-08-18 21:31:03 【问题描述】:

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

将 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);
    

我怀疑压缩在某种程度上是罪魁祸首。我正在使用来自SharpZipLib 的DeflaterOutputStream 类,它应该实现行业标准的放气算法,所以这里可能有一些设置我错了?

可以使用SAML2.0 Debugger(它是一个有用的在线转换工具)来测试编码输出。当我使用这个工具对我的输出进行解码时,结果是一派胡言。

因此问题是:您知道如何将 SAML 字符串转换为正确压缩和编码的 SAMLRequest 查询参数吗?

谢谢

编辑 1

下面接受的答案给出了问题的答案。这是所有后续 cmets 和答案更正的最终代码。

编码 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));
    

【问题讨论】:

我无法以您的“解码 SAMLResponse - 工作代码”的方式使用它。我必须将输入与输出 MemoryStream 分开。在执行 DeflateStream 之前,我还必须进行 UrlDecode 和 Convert.FromBase64String(...)。只是想我会做一个笔记,以防它帮助下一个人。 你能发布你的工作代码吗? 嘿 Randall,您能否提供您对“解码 SAMLResponse - 工作代码”所做的更改的代码?那将不胜感激!谢谢 @RandallBorck 请发布您的解决方案作为答案! 抱歉,我没有足够的声誉来审核对问题的编辑。但是我注意到您的编辑已被其他三位审阅者拒绝,给出的原因是:1.“回答问题,不要编辑代码。这就是问题的目的。” 2.“如果你有答案,请发表答案!” 3.“本次编辑对原帖改动太大,会失去原帖的意思或原意。”。因此,我建议您将代码发布为答案,而不是尝试编辑问题。 【参考方案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 往返执行正确。该错误必须稍后发生。也许 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 不被“调试器”站点接受。不可否认,压缩是有效的,因为它设法正确解压缩数据。它只是算法上有一些差异,但我不知道这个放气和那个放气之间有什么区别,d'oh。

【讨论】:

您提供的调试器站点显示了编码消息的示例。我尝试使用 Deflate 和 GZip 对其进行解压缩 - 但这是不可能的.. 甚至 .Net 的 GZipStream 也无法解压缩.. 它使用什么压缩算法? 我找到了一个适用于示例站点的压缩器,并且我再次更新了答案。请重读最后一部分:)【参考方案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());
    
  

【讨论】:

这是完美的答案!

以上是关于如何使用 C# 正确准备“HTTP 重定向绑定”SAML 请求的主要内容,如果未能解决你的问题,请参考以下文章

如何验证 HTTP 重定向绑定的 SAML 签名

使用 Java 创建“HTTP 重定向绑定”SAML 请求

SAML HTTP 重定向绑定中未保留查询字符串

单一登录 (SAML 协议)

如何使用 C# 找出安装在 SQL Server 中的 SSIS?

如何使用 C# 正确写入 Access (2002-2003) 数据库