Uncategorized

ASP.NET Web Api で Custom Basic 認証を使用してクラウド (Azure) に配置する

環境 : Visual Studio 2010, Azure SDK 1.4

REST サービス / Web Api の実践

ここでは、応用的なテーマをとりあげます。基本的な構築手順については、「Getting Started with ASP.NET Web Api」 (ASP.NET の場合)、または「REST サービスの作成」 (WCF の場合) を参照してください。

こんにちは。

勝手に連載化してしまい、すみません。REST サービスの構築について、現実の開発シナリオに即して、気づいた点を、適宜、記載していきたいと思います。(以前記載した内容も連載の一部に組み込みたいと思います。)

せっかくクラウドに配置するならクレーム ベースのほうがカッコ良いですが、今日は、REST サービスで Basic 認証をおこなう方法について記載します。
もちろん、ただ Basic 認証をおこなうだけなら設定 1 つで可能ですが、サービスを公開する上では、いくつか考えなければならない組み合わせが出てきますので、改めて、記載してみたいと思います。

追記 : 最新の ASP.NET Web API では、Windows Azure Active Directory を使用して Web API をセキュアにする方法もあります。詳細は「Windows Azure Active Directory の SSO 開発 (Visual Studio 2013 編)」を参照してください。

まず、「Basic 認証 (Basic Authentication) を使うと、Windows アカウント (Windows Identity) しか使えない」 と、まだ思われている方は、かなり昔に記述した こちら を参照してください。現実の公開される REST サービスで Windows アカウントは使わないと思いますが、Basic 認証では、Windows アカウント以外を使ったカスタマイズが可能です。(ただし、WCF を使用した場合です。)

補足 : なお、Web アプリケーション (ASP.NET) と共に使用する REST サービス (JavaScript などから呼び出すサービス)の場合は、ASP.NET MVC で JsonResult を使用したコントローラーを構築すれば OK です。(2012/03 追記 : ASP.NET MVC 4 以降では、ASP.NET Web API を使うことができます。)

こうしたカスタムの Basic 認証を使った REST サービスの公開をおこないたい場合、以下の 2 つの方法があります。

  • 上記で紹介した通り (上記のリンク参照)、WCF の Basic 認証で Windows アカウント以外を使用する方法です。
    この方法の場合、IIS ではなく、WCF のフレームワーク レベルでおこなわれる認証のため、SOAP ベースの WCF サービスなど、ほとんどの WCF サービスで使用できるというメリットがあります。一方、デメリットとしては、WCF だけでしか使用できない点と、上記のリンク先のページでも記載している通り、IIS ではなく、セルフ ホスト (self host) を使う必要があるという点です。
    このため、Windows Azure などのクラウド上に配置する場合は、Worker Role を作成して、ここで WCF をホスト させる必要があります。(Input ポートなども空けておく必要があります。この方法については、以前、こちら で記載した手順と同様です。)
    なお、この方法でも、後述する Windows Azure SQL Database (旧 SQL Azure) 上のメンバーシップ プロバイダーなどと組み合わせることができます。
  • もう 1 つは、REST サービスだからこそ可能ですが、IIS のカスタム モジュールを使って、カスタム (Custom) の Basic 認証をおこなう方法です。(REST サービスは、HTTP に準拠したシンプルな接続方法ですので、こうしたカスタマイズや、チューニングなども容易です。) このため、Web Role にホスト できます。
    かなり以前 (約 3 年前) に紹介した、こちら の MSDN の記事 (こちら の投稿からのリンクしている内容です) に概略を記載していますが、今回、改めて、この方法で、REST サービスを Windows Azure 上にホストしてみましょう。

今回は、後者の方法で REST サービスを構築して、Windows Azure 上に配置してみましょう。
このため、今回のサンプルでは、Windows アカウントを使用する代わりに、Windows Azure SQL Database 上に配置したメンバーシップ (membership) データベースを使用します。

なお、ここではサンプル コードとして紹介していますが、日本の開発者の方 (坂本さん) が構築された ASP.NET HTTP Authentication Module を NuGet から取得して使用できます。ASP.NET HTTP Authentication Module は、Basic 認証だけでなく、Digest 認証にも対応しています。(2012/03 追記)

 

メンバーシップ (Membership) データベースの作成

2013/04 追記 : ここではメンバーシップ・プロバイダー (SQL Server メンバーシップ・プロバイダー) を使用した方法を紹介していますが、Visual Studio 2012 の ASP.NET MVC 4 インターネット アプリケーションや ASP.NET Web Form、ASP.NET Web Site (Razor) など、最新の Web プロジェクトでは、ここで述べるメンバーシップ・プロバイダーを使用せず、Entity Framework のコード・ファーストを使ったクラス構成 (ユーザー情報を格納するクラスなど) をデータベースに反映したシンプルなものです。(なお、作成されるいくつかのテーブルについては、WebMatrix.WebData.dll の中でクラス定義されているため、プログラマーからは直接見えません。)この方法だと、ストアド・プロシージャなども使用せず、単に接続先を SQL Database に変更するだけで容易にクラウド環境に対応できます。

まず、Windows Azure SQL Database 上に ASP.NET のメンバーシップ プロバイダー用のデータベースを構築します。
下記のスクリプトを使用します。(インストールに関する細かな留意点などは、こちら を参照してください。ASP.NET の多くのプロバイダーが Windows Azure SQL Database を使って動かせますが、Session State プロバイダーは、Azure SQL Database では使用できませんので注意してください。)

[MSDN Archive] KB2006191 – Updated ASP.NET scripts for use with Windows Azure SQL Database  :

http://archive.msdn.microsoft.com/KB2006191

今回は、認証を動かすだけなので、上記の InstallMembership.sql を使用します。SQL Client を使って、下記の通り、実行します。(下記で、AAAAAAAAAA には Windows Azure SQL Database の名前空間を、testuser / XXXXXXX には Windows Azure SQL Database へのログイン ID / パスワードを設定してください。)

rem -- aspnetdb のデータベースを作成sqlcmd -S AAAAAAAAAA.database.windows.net -U testuser@AAAAAAAAAA -P XXXXXXX -i InstallAzure.sqlrem -- aspnetdb に、共通スキーマをインストールsqlcmd -S AAAAAAAAAA.database.windows.net -d aspnetdb -U testuser@AAAAAAAAAA -P XXXXXXX -i InstallCommon.sqlrem -- aspnetdb に、メンバーシップ データベースをインストールsqlcmd -S AAAAAAAAAA.database.windows.net -d aspnetdb -U testuser@AAAAAAAAAA -P XXXXXXX -i InstallMembership.sql

補足 : Windows Azure SQL Database への接続では、TCP が使用されます。企業のプロキシー設定などで、TCP をブロックしている場合は、上記の方法では接続できないので注意してください。(この場合、Windows Azure SQL Database の管理ポータルを使用して、スクリプトを実行してください。)

 

カスタムの Http Module の作成

今回は、カスタム (Custom) の Http Module を、アプリケーションと同一のプロジェクト内に構築します。

まず、[Windows Azure Project] を新規作成します。(今回、ターゲット フレームワークとして .NET Framework 4 を使用します。)
このあとの開発のため、ロールとして [WCF Services Web Role] を追加し、この Web Role のプロジェクトで [追加] – [新しい項目] を選択して、[クラス] を追加します。(今回、この名前を MyBasicAuthModule.cs とします。)

上記のリンクで紹介しているソースコード (昔のコード) と同様の方法で、下記の通り実装します。(ただし、上記のコードをそのままコピーしたんですが、ちゃんと動かなかったので、一部変更しました。ごめんなさい。。。)
下記の通り、IHttpModule インタフェースを実装します。この IHttpModule では、Init と Dispose だけ実装すれば良く、Init 処理で、必要なイベント処理を追加して実装します。
(なお、下記の ValidateCredentials 関数は、あとで作成します。)

. . .using System.Security.Principal;using System.Text;. . .public class MyBasicAuthModule : IHttpModule{  public void Init(HttpApplication context)  {    context.AuthenticateRequest += new EventHandler(this.AuthenticateUser);  }  public void Dispose()  {  }  public void AuthenticateUser(Object source, EventArgs e)  {    HttpApplication application = (HttpApplication)source;    HttpContext context = application.Context;    String userName = null;    String password = null;    // memo :    // This function is called for each request.    // So, it's better to use cache, etc ...    // (Here, I skipped this functionality.)    // 1. Get username and password from authorizationHeader    // 2. Validate username and password    String authorizationHeader = context.Request.Headers["Authorization"];    if (!ExtractBasicCredentials(      authorizationHeader, ref userName, ref password) ||      !ValidateCredentials(userName, password))    {      // Failed : request basic auth using header      context.Response.StatusCode = 401;      context.Response.AddHeader("WWW-Authenticate", "Basic realm ="demo"");    }    else    {      // Succeeded : create and set user principal object      context.User = new GenericPrincipal(new GenericIdentity(userName), null);    }    return;  }  // Get username and password from Http header (true = success, false = fail)  protected bool ExtractBasicCredentials(    String authorizationHeader, ref String username, ref String password)  {    const String HttpBasicSchemeName = "Basic";    if ((authorizationHeader == null) || (authorizationHeader.Equals(String.Empty)))      return false;    String verifiedAuthorizationHeader = authorizationHeader.Trim();    if (verifiedAuthorizationHeader.IndexOf(HttpBasicSchemeName) != 0)      return false;    // Get sub string (eliminated the first "Basic" string)    // from verifiedAuthorizationHeader    verifiedAuthorizationHeader =      verifiedAuthorizationHeader.Substring(        HttpBasicSchemeName.Length,        verifiedAuthorizationHeader.Length - HttpBasicSchemeName.Length).Trim();    // Decode the base64 encoded string    byte[] credentialBase64DecodedArray =      Convert.FromBase64String(verifiedAuthorizationHeader);    UTF8Encoding encoding = new UTF8Encoding();    String decodedAuthorizationHeader =      encoding.GetString(credentialBase64DecodedArray,        0,        credentialBase64DecodedArray.Length);    // Get username and password string    int separatorPosition = decodedAuthorizationHeader.IndexOf(':');    if (separatorPosition <= 0)      return false;    username =      decodedAuthorizationHeader.Substring(0, separatorPosition).Trim();    password =      decodedAuthorizationHeader.Substring(separatorPosition + 1,      (decodedAuthorizationHeader.Length - separatorPosition - 1)).Trim();    if (username.Equals(String.Empty) || password.Equals(String.Empty))      return false;    return true;  }  // Validate username and password (true = Valid, false = Invalid)  protected bool ValidateCredentials(String userName, String password)  {    // We'll create this later . . .  }}. . .

上記では、毎回、Web ページが Request されるたびに、AuthenticateUser メソッドが呼び出されます。
また、認証に成功した際に、単純に GenericPrincipal オブジェクトのみを設定していますが、ユーザーに関するその他の情報 (Principal) を設定する場合には、ここで設定をおこなってください。(ユーザー プロファイルなどを扱う場合には、上記で、メンバーシップ データベースだけでなく、プロファイル データベースなどもインストールしておいてください。)

さいごに、上記の ValidateCredentials メソッドを作成します。
今回は、上記の ValidateCredentials メソッドで、Windows Azure SQL Database 上に準備しておいたメンバーシップ (Membership) データベースを参照して認証の確認をおこないます。接続先のメンバーシップ プロバイダーについては、このあとで、Web.config 上に設定し、下記 (太字) のコードでは、この構成ファイル (.config) に設定されたプロバイダー情報を取得しています。(下記の通り、非常に簡単に取得できます。)

補足 : もちろん、ここで、独自なストア (クラウド上の別のデータベースなど) から情報を取得して、独自な認証処理を記述して頂いて構いません。

. . .using System.Web.Security;. . .// Validate username and password (true = Valid, false = Invalid)protected bool ValidateCredentials(String userName, String password){    // Get default provider    // (if named provider, use Membership.Provider["<Provider Name>"] )    MembershipProvider prov = Membership.Provider;    return prov.ValidateUser(userName, password);}. . .

さいごに、Web Role のプロジェクトの Web.config を開き、下記太字の通り追記します。

ここでは、HTTP の要求の際に実行される上記のモジュール (MyBasicAuthModule クラス) の設定と、上記の ValidateCredentials メソッドで使用している「既定 (Default) のメンバーシップ プロバイダー」の設定をおこなっています。(下記で、AAAAA には Windows Azure SQL Database の名前空間を、testuser / XXX には Windows Azure SQL Database へのログイン ID / パスワードを設定してください。)

<?xml version="1.0"?><configuration>  <configSections>  </configSections>  . . .  <connectionStrings>    <add name="SqlConn"         connectionString="Data Source=AAAAA.database.windows.net;           Initial Catalog=aspnetdb;User Id=testuser@AAAAA;Password=XXX"         providerName="System.Data.SqlClient"/>  </connectionStrings>  . . .  <system.web>    <compilation debug="true" targetFramework="4.0" />    <membership defaultProvider="SqlAzureMemProvider" userIsOnlineTimeWindow="15">      <providers>        <clear />        <add name="SqlAzureMemProvider"             type="System.Web.Security.SqlMembershipProvider"             connectionStringName="SqlConn"             applicationName="MyRestService"             enablePasswordRetrieval="false"             enablePasswordReset="false"             requiresQuestionAndAnswer="false"             requiresUniqueEmail="false"             passwordFormat="Hashed" />      </providers>    </membership>  </system.web>  . . .  <system.webServer>    <modules runAllManagedModulesForAllRequests="true">      <add name="MyBasicAuthModule"           type="WCFServiceWebRole1.MyBasicAuthModule" />    </modules>  </system.webServer></configuration>

補足 : SqlMembershipProvider を使用したことがある方はご存じかと思いますが、上記 (Web.config) で、applicationName 属性は重要です。ユーザー認証の確認 (ValidateUser) では、ユーザー ID / パスワードだけでなく、applicationName も参照します。

 

アプリケーションの作成と配置

以上で、今回説明したい部分のほとんどは、完了です。

あとは、REST Service (Web Api) を構築します。
ここでは、この構築方法は省略しますが、構築手順の詳細については、こちら (この中の「REST サービスの作成」) を参考にしてください。細かな制御が必要な場合は、まだ Preview 版ですが、WCF Web API (旧 WCF REST Starter Kit) を使用すると良いでしょう。また、ASP.NET Web Api を使用して Web API (RESTful Service) を構築することもできます。(2012/02 変更)

補足 : この他に、ユーザー情報の登録・変更など、メンテナンス用の画面の開発なども必要になりますが、今回は、この構築については省略します。なお、上記の Windows Azure SQL Database のメンバーシップ データベースは、上記の通り .config に設定して、ASP.NET の通常のメンバーシップを扱った開発でも使用できますので、ASP.NET の標準的な開発手順で こうした画面を構築できます。

あとは、いつもの通り、以下の手順で、上記のアプリケーションを Windows Azure に配置します。

  1. Windows Azure にホステッド サービス (Hosted Service) を作成します
  2. 上記のプロジェクトのパッケージ (Package) を作成します
  3. ホステッド サービスに、このパッケージを配置する

 

実際の開発では、一度 ログインした情報などをキャッシュする仕組みなど (HttpContext.Current.Cache によるメモリ ヒープを使ったキャッシュ、など)、さまざまな処理を実装してください。(ここでは、必要最小限度の処理のみを構築しています。)
また、この方法は、同様に、WCF Data Services でも使用できます。

 

 

 

Categories: Uncategorized

Tagged as: ,

10 replies»

Leave a Reply