.NET 4.5 で Active Authentication を実装する


環境 : Visual Studio 2012 RC (.NET Framework 4.5 RC), AD FS 2.0 (Active Directory Federation Services 2.0), Windows Azure Active Directory – Access Control Service 2.0

こんにちは。

WIF (Windows Identity Foundation) は、.NET Framework 4.5 からは、.NET 標準のライブラリーとして組み込まれています。

いずれ このブログでも紹介したいと思いますが、クラス構成も いくつか変更 (追加、変更、削除) がおこなわれています。(こちら など、本ブログでも、Visual Studio 11 Beta 版以降、いくつかコメントを追記しておきました。)

そうした変更の中で、1 つ やっかいなのが、これまで WIF で提供されていた WCF の WS-Trust Binding が無くなったことです。
これまで、Active Authentication (Active 認証) を実装する場合などに、こうしたクラスを使用してきましたが、今後はどうすれば良いのでしょうか ?

Tech Ed US のセッション (What’s New in Windows Identity Foundation in Microsoft .NET Framework 4.5) の中で Vittorio が解説しているように、今後は (.NET 4.5 以降は)、こうした Binding を自分で構成してください !
WCF における Binding と Channel の基礎」で解説しているように、要件に応じ、必要な BindingElement を組み合わせて、Custom Binding を構成します。

ということで、実際、どのように構成するのか、サンプル コードで紹介したいと思います。
今回は、以前 作成した「AD FS、ACS フェデレーションを処理する Client Programming」のサンプル コードを、.NET Framework 4.5 で書き換えてみます。

ADFS で認証をおこなって SAML Token を取得し、この受け取った Token (Assertion) を使って ACS で認証し、ACS から受け取った SAML Token を使って、さいごに、Relying Party (RP) である Web アプリケーション (今回は https://testapp.cloudapp.net/ とします) で認証します。ブラウザーを使っている場合は、これらの処理は STS とブラウザーがすべておこなってくれますが、この流れをプログラム コードで処理する場合は、Active Authentication と呼ばれる方式でアクセスします。(事前の設定方法などは、この投稿では省略します。「AD FS、ACS フェデレーションを処理する Client Programming」を参照してください。)

.NET Framework 4.5 を使った場合の Active Authentication のサンプル コードは、下記の通りです。(プロジェクトに、System.IdentityModel.dll、System.IdentityModel.Services.dll、System.ServiceModel.dll、System.ServiceModel.Channels.dll、System.Runtime.Serialization.dll を参照追加してください。)
下記 太字の通り、今回から、必要な Binding を、BindingElement を組み合わせて独自に構成します。(ここで登場する TransportBindingElement、EncodingBindingElement などについては、「WCF における Binding と Channel の基礎」を参照してください。)

. . .
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.IdentityModel.Protocols.WSTrust;
using System.IO;
using System.Xml;
. . .

static void Main(string[] args)
{
  //
  // Create ws-trust binding using Windows auth (Negotiate)
  //
  BindingElementCollection elems1 = new BindingElementCollection();
  SecurityBindingElement secElem1 =
    SecurityBindingElement.CreateSspiNegotiationOverTransportBindingElement(true);
  secElem1.MessageSecurityVersion =
    MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
  elems1.Add(secElem1);
  TextMessageEncodingBindingElement encElem1 =
    new TextMessageEncodingBindingElement();
  encElem1.ReaderQuotas.MaxArrayLength = 0x200000;
  encElem1.ReaderQuotas.MaxStringContentLength = 0x200000;
  elems1.Add(encElem1);
  HttpTransportBindingElement tranElem1 =
    new HttpsTransportBindingElement()
    {
      AuthenticationScheme =
        AuthenticationSchemes.Negotiate
    };
  elems1.Add(tranElem1);
  CustomBinding MyWindowsWSTrustBinding =
    new CustomBinding(elems1);

  //
  // get SAML token from AD FS
  //
  WSTrustChannelFactory idpFactory = new WSTrustChannelFactory(
    MyWindowsWSTrustBinding,
    new EndpointAddress(new Uri(@"https://adfsserver.testdomain.com/adfs/services/trust/13/windowsmixed"),
      EndpointIdentity.CreateSpnIdentity(@"host/machine01.testdomain.com")));
  idpFactory.TrustVersion = System.ServiceModel.Security.TrustVersion.WSTrust13;
  idpFactory.Credentials.Windows.ClientCredential =
    CredentialCache.DefaultNetworkCredentials;
  RequestSecurityToken reqToken1 = new RequestSecurityToken(
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue",
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer");
  reqToken1.AppliesTo = new EndpointReference(@"https://adfstest.accesscontrol.windows.net/v2/wstrust/13/issuedtoken-symmetric");
  reqToken1.TokenType = @"urn:oasis:names:tc:SAML:2.0:assertion";
  WSTrustChannel idpProxy = (WSTrustChannel)idpFactory.CreateChannel();
  System.IdentityModel.Tokens.SecurityToken issuedToken1 = idpProxy.Issue(reqToken1);
  idpFactory.Close();

  //
  // Create ws-trust binding using Issued token
  //
  BindingElementCollection elems2 = new BindingElementCollection();
  IssuedSecurityTokenParameters issuedParams =
    new IssuedSecurityTokenParameters()
    {
      KeyType =System.IdentityModel.Tokens.SecurityKeyType.BearerKey,
      //IssuerMetadataAddress = null,
      KeySize = 0
    };
  XmlDocument xdoc1 = new XmlDocument();
  XmlElement xelem1 = xdoc1.CreateElement(
    "trust",
    "EncryptionAlgorithm",
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512");
  xelem1.AppendChild(
    xdoc1.CreateTextNode(
      SecurityAlgorithmSuite.Basic256.DefaultEncryptionAlgorithm));
  issuedParams.AdditionalRequestParameters.Insert(
    0,
    xelem1);
  XmlDocument xdoc2 = new XmlDocument();
  XmlElement xelem2 = xdoc2.CreateElement(
    "trust",
    "CanonicalizationAlgorithm",
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512");
  xelem2.AppendChild(
    xdoc2.CreateTextNode(
      SecurityAlgorithmSuite.Basic256.DefaultCanonicalizationAlgorithm));
  issuedParams.AdditionalRequestParameters.Insert(
    0,
    xelem2);
  SecurityBindingElement secElem2 =
    SecurityBindingElement.CreateIssuedTokenOverTransportBindingElement(issuedParams);
  secElem2.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
  secElem2.IncludeTimestamp = true;
  secElem2.MessageSecurityVersion =
    MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
  RsaSecurityTokenParameters rsaParam = new RsaSecurityTokenParameters();
  rsaParam.InclusionMode = SecurityTokenInclusionMode.Never;
  rsaParam.RequireDerivedKeys = false;
  secElem2.OptionalEndpointSupportingTokenParameters.Endorsing.Add(rsaParam);
  elems2.Add(secElem2);
  TextMessageEncodingBindingElement encElem2 =
    new TextMessageEncodingBindingElement();
  encElem2.ReaderQuotas.MaxArrayLength = 0x200000;
  encElem2.ReaderQuotas.MaxStringContentLength = 0x200000;
  elems2.Add(encElem2);
  HttpTransportBindingElement tranElem2 =
    new HttpsTransportBindingElement();
  elems2.Add(tranElem2);
  CustomBinding MyIssuedTokenWSTrustBinding =
    new CustomBinding(elems2);

  //
  // get SAML token from ACS
  //
  WSTrustChannelFactory acsFactory = new WSTrustChannelFactory(
    MyIssuedTokenWSTrustBinding,
    new EndpointAddress(
      new Uri(@"https://adfstest.accesscontrol.windows.net/v2/wstrust/13/issuedtoken-symmetric")));
  acsFactory.TrustVersion = System.ServiceModel.Security.TrustVersion.WSTrust13;
  acsFactory.Credentials.SupportInteractive = false;
  RequestSecurityToken reqToken2 = new RequestSecurityToken(
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue",
    @"http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer");
  reqToken2.AppliesTo = new EndpointReference(@"https://testapp.cloudapp.net/");
  reqToken2.TokenType = "urn:oasis:names:tc:SAML:2.0:assertion";
  WSTrustChannel acsProxy =
    (WSTrustChannel)acsFactory.CreateChannelWithIssuedToken(issuedToken1);
  RequestSecurityTokenResponse securityTokenResponse2;
  acsProxy.Issue(reqToken2, out securityTokenResponse2);
  acsFactory.Close();

  //
  // login to RP (and, get FedAuth cookie)
  //
  string FedAuthValue = string.Empty;
  string FedAuth1Value = string.Empty;
  StringBuilder rstrSb = new StringBuilder();
  System.Xml.XmlTextWriter rstrWr =
    new System.Xml.XmlTextWriter(new StringWriter(rstrSb));
  WSTrustFeb2005ResponseSerializer rstrSr =
    new WSTrustFeb2005ResponseSerializer();
  rstrSr.WriteXml(securityTokenResponse2,
    rstrWr,
    new WSTrustSerializationContext());
  string rstrStr = rstrSb.ToString();
  HttpWebRequest webRequest1 =
    HttpWebRequest.Create("https://testapp.cloudapp.net/") as HttpWebRequest;
  webRequest1.Method = "POST";
  webRequest1.ContentType = "application/x-www-form-urlencoded";
  webRequest1.CookieContainer = new CookieContainer();
  webRequest1.AllowAutoRedirect = false;
  string dataStr = string.Format(
    "wa=wsignin1.0&wresult={0}&wctx=rm%3D0%26id%3Dtest%26ru%3D%252f",
    System.Web.HttpUtility.UrlEncode(rstrStr));
  byte[] dataByte = Encoding.UTF8.GetBytes(dataStr);
  using (Stream reqStream1 = webRequest1.GetRequestStream())
  {
    reqStream1.Write(dataByte, 0, dataByte.Length);
    reqStream1.Close();
  }
  using (HttpWebResponse webResponse1 =
    webRequest1.GetResponse() as HttpWebResponse)
  {
    FedAuthValue = webResponse1.Cookies["FedAuth"].Value;
    FedAuth1Value = webResponse1.Cookies["FedAuth1"].Value;
  }

  //
  // Call test
  //
  string result = string.Empty;
  CookieContainer cc = new CookieContainer();
  cc.Add(new Cookie("FedAuth", FedAuthValue)
  {
    Path = "/",
    Domain = "testapp.cloudapp.net"
  });
  cc.Add(new Cookie("FedAuth1", FedAuth1Value)
  {
    Path = "/",
    Domain = "testapp.cloudapp.net"
  });
  HttpWebRequest webRequest2 =
    HttpWebRequest.Create("https://testapp.cloudapp.net/testpage.aspx")
      as HttpWebRequest;
  webRequest2.Method = "GET";
  webRequest2.CookieContainer = cc;
  using (HttpWebResponse webResponse2 =
    webRequest2.GetResponse() as HttpWebResponse)
  {
    using (StreamReader resReader2 =
      new StreamReader(webResponse2.GetResponseStream()))
    {
      result = resReader2.ReadToEnd();
      resReader2.Close();
    }
  }

}
. . .

今回は、ADFS の認証の際に Windows 認証を使用していますが、UserName 認証を使用する場合 (ADFS Proxy を使用する場合 など) は、構成方法が異なります。(AuthenticationSchema も Basic などを使用します。)
また、上記は BearerKey の場合ですが、SymmetricKey の場合は、IssuedSecurityTokenParameters の AdditionalRequestParameters に Signature (署名) と Encryption (暗号化) のための要素 (XML) も追加する必要があります。(さらに、IssuedSecurityTokenParameters の KeySize も、SecurityAlgorithmSuite の DefaultSymmetricKeyLength などを設定します。)

要件に応じて、適宜、必要な Binding を構成する必要があるので、注意してください。

なお、この投稿だけ読むと、WIF 4.5 は不便になったように誤解されるかもしれませんが、WIF 4.5 では、さまざまな点が改良され、より扱いやすくなっています。
WIF 4.5 や新しい Identity and Access Tool については、また次回、このブログでも簡単に紹介したいと思います。

 

Comments (4)

  1. KAW says:

    参考になる情報ありがとうございます。

    Metroアプリで上記サンプルを実装しようとしても、デフォルトで参照設定されている「.NET for Metro style apps」から使用できないクラス・メソッド等が多々あるようです。

    (ex.SecurityBindingElement.CreateSspiNegotiationOverTransportBindingElement)

    参照先のディレクトリが「.NETCore」か「.NETFramework」なのかの違いによるモノだと思うのですが、MetroアプリからADFSの認証を行うサンプル等はないのでしょうか?

  2. コメントありがとうございます。ご質問の件ですが、下記の Tech Ed 2012 US のセッション「Building Windows 8 Apps for the Enterprise」で紹介されていますが、現状は、WebAuthenticationBroker を使用して実装してください。(Slide 10 参照。)

    ただし、こちらは、Passive 方式での認証となります。

    channel9.msdn.com/…/DEV358

    WebAuthenticationBroker を使ったダウンロードサンプルは提供されているのですが、すみません、現在、まだ ADFS を使ったサンプルは提供されていないようです。

  3. Windows 8 Store App から Office 365 に接続するコードのサンプルが以下に紹介されました。

    WCF のクラスも使用できないため自力でプロトコルの処理を記述するものですが、ダウンロード サンプルとして提供してくれていますので是非ご活用ください。(なお、Office 365 Preview に接続するサンプルです)

    blogs.msdn.com/…/windows-8-store-apps-office-365-enterprise-preview-sharepoint-online.aspx

  4. ferari poker says:

    What’s up, I read your .NET 4.5 で Active Authentication を実装する | 松崎 剛 Blog blogs daily. Your story-telling style is witty, keep up the good work! ferari poker http://feraripoker.com/

Skip to main content