21

HTTP リダイレクト バインディング メソッドを使用して、SP が開始した SAML 2.0 認証トランザクションを作成する必要があります。これは非常に簡単です。IdP URI を取得し、単一のクエリ文字列 param を連結するだけ SAMLRequestです。param は、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);
    }
}

なんらかの理由で圧縮が原因だと思います。業界標準の deflate-algorithm を実装することになっているSharpZipLibDeflaterOutputStreamのクラスを使用しているので、ここにいくつかの設定が間違っている可能性がありますか?

エンコードされた出力は、このSAML2.0 Debugger (便利なオンライン変換ツール) を使用してテストできます。このツールを使用して出力をデコードすると、意味がありません。

したがって、問題は次のとおりです。SA​​ML 文字列を正しくデフレートおよびエンコードされた 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 された変数を生成し、デバッガーを適切に通過します。標準の .Net の名前空間DeflateStreamに由来します。System.IO.CompressionSharpZip の Deflate が「デバッガー」サイトで受け入れられない理由がまったくわかりません。データを適切に解凍できるため、圧縮が機能することは否定できません..アルゴリズムの違いである必要がありますが、このデフレートとそのデフレートの違いは何ですか.

于 2012-08-23T11:42:02.757 に答える
12

上部の質問には「Decode SAMLResponse - Working Code」セクションが含まれていますが、そのコードは壊れているように見えました。いくつか試してみたところ、同じストリームを同時に読み書きしようとしていることがわかりました。読み取りストリームと書き込みストリームを分離して作り直しました。これが私の解決策です (利便性と明確さのために要求セクションを提供しています)。

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 に答える