2012年8月2日

Windows8とVS2012とACS。(5)

何もかもこの夏の暑さのせいです

ようやっとプロジェクトの設定を変更するときが来ました。
長かったです。
……もっと短くまとめられるはずなんですけどねー。
Microsoftのエバンジェリストの方々はすごいです。

ACSの設定忘れ

ACSの設定で、1か所設定しなければいけない部分を設定し忘れていました。
それは、「規則グループ」です。
これを設定していないと、ACSはIDプロバイダから何を取得していいのかわからず、エラーになります。
まずはACSの設定ポータルで「規則グループ」を選び、自動で作成されている規則を選択します。
acstest32
選択したら、「ルール」の欄に「生成」のリンクがありますので選択します。
acstest33
デフォルトの状態で規則を生成してくれますので、全てのIDプロバイダにチェックが入っている状態で「生成」をクリックします。
acstest34
最後に、真ん中近辺にある「保存」のボタンをクリックして生成した規則を保存しておきます。
これでIDプロバイダから指定のClaimが取得できるようになります。

VisualStudio2012のプロジェクトの設定

ここまででACSの設定は済んでいますよね。
基本的なところはデフォルトにしてしまったので、特に難しいものでもなかったと思います。
さて、次はそのACSを使用するようにプロジェクトを設定していくことにします。
今回は画像少な目に……。少な目に……(涙目)

ルートページの作成

何はなくとも、/ で表されるルートのページを作っておきましょう。
ASP.NET MVC4を選びましたので、コントローラーを作成していけばページは作られます。
前回作成したプロジェクト内にControllersというディレクトリがありますので、そこを右クリックし、「追加」→「コントローラー」を選択します。
acstest20
上記のようなダイアログが表示されますので、「コントローラー名」の欄にHomeControllerとなるように入力、また、テンプレートはEmpty MVC controllerを選び「追加」をクリックします。
次に、Viewsディレクトリで右クリックし、「追加」→「新規ディレクトリ」を選択します。
すると、新しいディレクトリが作成されますので、ディレクトリ名をHomeに変更します。
そして、今変更したHomeディレクトリで右クリックし、「追加」→「表示...」を選択し、ビュー名をIndexとしたファイルを作成します。
acstest24
作成の際に、「レイアウトまたはマスターページを使用する」の下の欄に_Layout.cshtmlを選択するように設定しておきます。
これにより、レイアウトにテンプレート的な何かを使用することができるようになります。
(この場合、_Layout.cshtmlファイルですね)
acstest23
これで / の設定は終わりました。
ためしにF5でデバッグをしてみると、「Index」と書いたページが表示されます。
基本的にASP.NET MVC4に関することは今回は扱いません。
ざっと手順だけ書きますので、なぜそういうことをしているのかは自分で勉強してください。
(気が向いたらまた書くかもしれませんが)

ログインページの作成

さて、やっとログインのページを作成するときが来ました。
ACSの設定からもわかるように、ログイン関係はLoginControllerの1ファイルで済ませるつもりです。
先ほどのHomeControllerと同様に、LoginControllerを作成し、次のような関数を作成します。
    public class LoginController : Controller
    {

        public ActionResult Index()
        {
            string returnUrl = this.Request.Params["ReturnUrl"];
            if (string.IsNullOrEmpty(returnUrl))
            {
                returnUrl = "/";
            }
            Session["login_return_url"] = returnUrl;
            return View();
        }

        public ActionResult LoggedIn()
        {
            string returnUrl = (string)Session["login_return_url"];
            Session.Remove("login_return_url");
            if (!string.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return Redirect("/");
        }

        public ActionResult Error()
        {
            var t = this;
            Session.Remove("login_return_url");
            return Redirect("/");
        }

    }

ログイン後に表示するURLがパラメータになってわたってくるときがありますので、その対処のみ入れてあります。

次に、表示ファイルを作りますが、今度は Views\Login\Index.cshtml を以下の内容で作成します。
ここでは、実行状況を判定して、読み込むURLを変更しています。

acstest26

読み込み元はACSの「アプリケーション統合」→アプリケーション名→「オプション2」に記されているURLをコピペして記載し、JSONP用の戻り関数を「setProviders」に設定したものです。
アプリケーション名で選択する際、ローカルとWebSites用と両方とも辿って記載しています。
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Login</h2>
<div>
    <ul id="providers"></ul>
</div>

@section scripts {
    <script type="text/javascript">
        function setProviders(data) {
            for (var i = 0; i < data.length; i++) {
                $("#providers").append("<li><a href=\"" + data[i].LoginUrl + "\">" + data[i].Name + "</a></li>");
            }
        }
    </script>
    @if (Request.IsLocal)
    {
        <script type="text/javascript" src="https://acstst.accesscontrol.windows.net:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=http%3a%2f%2flocalhost%3a30994%2f&reply_to=http%3a%2f%2flocalhost%3a30994%2flogin%2floggedin&context=&request_id=&version=1.0&callback=setProviders"></script>
    }
    else
    {
        <script type="text/javascript" src="https://acstst.accesscontrol.windows.net:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=https%3a%2f%2facstest.azurewebsites.net%2f&reply_to=https%3a%2f%2facstest.azurewebsites.net%2flogin%2floggedin&context=&request_id=&version=1.0&callback=setProviders"></script>
    }
}

基本のソースコードはここまでです。

WIFを生成物に含めるようにする


前々回に設定したWIF3.5の環境。
実はDLLをプロジェクトに読み込んでやらなければいけません。
一応、WIFで認証処理されますので……。

プロジェクトの「参照設定」→「参照の追加」を選択。
出てきたダイアログ内で、「アセンブリ」→「拡張子」と選び、一覧の中から「Microsoft.IdentityModel」にカーソルを合わせ、名前の左に出てきた四角をクリックします。
すると、チェックがつきますので、OKをクリックします。

acstest27

ソリューションエクスプローラーに戻ってきたら、「詳細設定」内に増えている「Microsoft.IdentityModel」を右クリックし、プロパティを選びます。
プロパティ設定画面が表示されますので、「ローカルコピー」の欄をTrueに変更します。
こうすることで、Microsoft.IdentityModelが生成物の中に含められ、使用できるようになります。
(WebSitesにはデフォルトでは入っていないのです)

acstest28

WIF SDKを使用してACSを使用するように設定する


すみません、今回超長いです。
まだまだやること沢山あります。
見捨てないください。

WIF SDKに入っているFedUtil.exeを使用して認証部分をACSを使用するように変更します。
FedUtil.exeは通常 C:\Program Files (x86)\Windows Identity Foundation SDK\v3.5 にあるはずです。

起動するとまずは変更するWeb.Configの位置と、領域のURLを入力します。
ここではまずローカル環境のものを設定しておきます。

acstest29

入力後、Nextを押すとhttpでセキュアじゃない、と言われますが気にせず続けます。
すると、ACSのWS-FederationのURLを入力する画面に切り替わるので、ACSの設定の「アプリケーション統合」に記載されているWS-FederationメタデータのURLを入力します。

acstest31

acstest30

あとはアプリが終了するまでNextやFinishやらのボタンを押していけばおっけー。
Web.Configがいろいろ書き換えられますが、まず手直しするのが以下の部分。

  </appSettings>
  <!--
  <location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  -->
  <system.web>
    <!--
    <authorization>
      <deny users="?" />
    </authorization>
    -->
    <authentication mode="Forms"><forms loginUrl="~/login" timeout="2880" /></authentication>
    <!--
    <authentication mode="None" />
    -->
    <httpRuntime requestValidationMode="2.0" />
    <compilation debug="true" targetFramework="4.0">

初期設定のままだと、どのページを見てもログインしていなければログインページにリダイレクトされます。
ちょっとウザいので、デフォルトリダイレクトはさせずに、ログインが必要な場合のとび先を /login になるように変更しておきます。
また、認証プロトコル的にエラーになるので、その回避(httpRuntimeの行)を行っておきます。
そして、重要な変更はだいたい以下のようにWeb.Configに追加されています。

  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="http://localhost:30994/" />
      </audienceUris>
      <federatedAuthentication>
        <wsFederation passiveRedirectEnabled="true" issuer="https://acstst.accesscontrol.windows.net/v2/wsfederation" realm="http://localhost:30994/" requireHttps="false" />
        <cookieHandler requireSsl="false" />
      </federatedAuthentication>
      <applicationService>
        <claimTypeRequired>
          <!--Following are the claims offered by STS 'https://acstst.accesscontrol.windows.net/'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
          <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
          <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
          <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />-->
          <!--<claimType type="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" optional="true" />-->
        </claimTypeRequired>
      </applicationService>
      <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedIssuers>
          <add thumbprint="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" name="https://acstst.accesscontrol.windows.net/" />
        </trustedIssuers>
      </issuerNameRegistry>
      <certificateValidation certificateValidationMode="None" />
    </service>
  </microsoft.identityModel>

ここで設定したのはローカルデバッグ環境用なので、WebSitesにデプロイする際の設定も行っておきます。
上記の部分をWeb.Configからコピーし、Web.Release.Configに貼り付けます。
そして、領域部分を書き換え、デプロイ時に反映されるように変更します。
変更後のWeb.Release.Configは以下のようになります。

<?xml version="1.0"?>

<!-- For more information on using Web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <!--
    In the example below, the "SetAttributes" transform will change the value of 
    "connectionString" to use "ReleaseSQLServer" only when the "Match" locator 
    finds an atrribute "name" that has a value of "MyDB".
    
    <connectionStrings>
      <add name="MyDB" 
        connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" 
        xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    </connectionStrings>
  -->
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
    <!--
      In the example below, the "Replace" transform will replace the entire 
      <customErrors> section of your Web.config file.
      Note that because there is only one customErrors section under the 
      <system.web> node, there is no need to use the "xdt:Locator" attribute.
      
      <customErrors defaultRedirect="GenericError.htm"
        mode="RemoteOnly" xdt:Transform="Replace">
        <error statusCode="500" redirect="InternalError.htm"/>
      </customErrors>
    -->
  </system.web>
  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="https://acstest.azurewebsites.net/" xdt:Transform="Replace" />
      </audienceUris>
      <federatedAuthentication>
        <wsFederation passiveRedirectEnabled="true" issuer="https://acstst.accesscontrol.windows.net/v2/wsfederation" realm="https://acstest.azurewebsites.net/" requireHttps="true" xdt:Transform="Replace" />
        <cookieHandler requireSsl="true" xdt:Transform="Replace" />
      </federatedAuthentication>
      <applicationService>
        <claimTypeRequired>
          <!--Following are the claims offered by STS 'https://acstst.accesscontrol.windows.net/'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
          <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
          <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
          <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />-->
          <!--<claimType type="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" optional="true" />-->
        </claimTypeRequired>
      </applicationService>
      <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedIssuers>
          <add thumbprint="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" name="https://acstst.accesscontrol.windows.net/" />
        </trustedIssuers>
      </issuerNameRegistry>
      <certificateValidation certificateValidationMode="None" />
    </service>
  </microsoft.identityModel>
</configuration>

実際のところ、必要なのは xdt:Transform="Replace" を付加した3行分だけなんですけど。
このように設定しておくことで、環境を切り替えることができるようになっています。

WebSites対応


さて、最後の大詰めです。
現状で動かしてみると、ローカルのデバッグ環境では問題なく動くのに、デプロイしてみるとログイン直後にエラーになってしまいます。
これはなにやら、セキュリティ的なエラーが発生しているらしいです。
(System.Security.Cryptography.CryptographicException)

ここはもう、そういうもんなのです。
……というわけにもいかず。
調べてみると、代替案があることがわかります。

VS2012に戻り、ソリューションエクスプローラーの「参照設定」を右クリックし、「NuGetパッケージの管理」を選びます。
NuGetが開いたなら、「identity」で検索をし、出てきた候補の中から「Thinktecture.IdentityModel」をインストールします。

acstest35

インストールが済んだら、Web.Configに以下の設定を追加します。

  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="http://localhost:30994/" />
      </audienceUris>
      <federatedAuthentication>
        <wsFederation passiveRedirectEnabled="true" issuer="https://acstst.accesscontrol.windows.net/v2/wsfederation" realm="http://localhost:30994/" requireHttps="false" />
        <cookieHandler requireSsl="false" />
      </federatedAuthentication>
      <applicationService>
        <claimTypeRequired>
          <!--Following are the claims offered by STS 'https://acstst.accesscontrol.windows.net/'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
          <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
          <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
          <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />-->
          <!--<claimType type="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" optional="true" />-->
        </claimTypeRequired>
      </applicationService>
      <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedIssuers>
          <add thumbprint="2362F91373ED4C69F2B9CBE528100F2B33386B78" name="https://acstst.accesscontrol.windows.net/" />
        </trustedIssuers>
      </issuerNameRegistry>
      <certificateValidation certificateValidationMode="None" />
      <!-- ここから追加 -->
      <securityTokenHandlers>
        <remove type="Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <add type="Thinktecture.IdentityModel.Web.MachineKeySessionSecurityTokenHandler, Thinktecture.IdentityModel" />
      </securityTokenHandlers>
      <!-- ここまで追加 -->
    </service>
  </microsoft.identityModel>

このあたりのことは、Dominick BaierさんのBlogに詳細が書かれています。
……ま、まあ、難しいことはわからないんだけどねっ!

夜中でも暑いです


なんとか駆け足でざっと書いてみましたが……。
ますますもって長い!
いらん事書きすぎです。
実際、慣れてしまえば10分かからない作業なのに……。

とりあえず、ここまでの変更でWindows8 + VisualStudio2012でWebSitesでも無事にACSを使ったサイトを作れるようになりました。
本当はこんなに苦労しなくても、Azureが.Net4.5環境に移行してくれればIdentity and Access toolsだけでかたが付くんですけど。ふぅ。

0 件のコメント:

コメントを投稿