21

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

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

  1. 构建 SAML 字符串
  2. 压缩这个字符串
  3. Base64 对字符串进行编码
  4. 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 调试器(它是一个有用的在线转换工具)来测试编码的输出。当我使用这个工具解码我的输出时,它就变成了无稽之谈。

因此,问题是:您知道如何将 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));
    }
}
4

2 回答 2

14

我刚刚使用您的示例 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。

于 2012-08-23T11:42:02.757 回答
12

顶部的问题包含“解码 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());
    }
  }
}
于 2013-04-09T18:34:26.887 回答