如何使用 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 请求的主要内容,如果未能解决你的问题,请参考以下文章