松崎 剛 Blog

This Blog's theme : エンタープライズ開発 (Server side)、Office サーバ開発

Web Api (REST サービス) で Custom Basic 認証を使用してクラウド (Windows Azure) に配置する

Web Api (REST サービス) で Custom Basic 認証を使用してクラウド (Windows Azure) に配置する

  • Comments 0

環境 :
Visual Studio 2010
Windows Azure SDK 1.4
(Windows Azure 利用方法は下記補足)

REST サービス / Web Api の実践

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

こんにちは。

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

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

まず、「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 ポートなども空けておく必要があります。この方法については、以前、こちら で記載した手順と同様です。)
    なお、この方法でも、後述する SQL Azure 上のメンバーシップ プロバイダーなどと組み合わせることができます。
  • もう 1 つは、REST サービスだからこそ可能ですが、IIS のカスタム モジュールを使って、カスタム (Custom) の Basic 認証をおこなう方法です。(REST サービスは、HTTP に準拠したシンプルな接続方法ですので、こうしたカスタマイズや、チューニングなども容易です。) このため、Web Role にホスト できます。
    かなり以前 (約 3 年前) に紹介した、こちら の MSDN の記事 (こちら の投稿からのリンクしている内容です) に概略を記載していますが、今回、改めて、この方法で、REST サービスを Windows Azure 上にホストしてみましょう。

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

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

 

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

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

[MSDN Archive] KB2006191 - Updated ASP.NET scripts for use with SQL Azure  :
http://archive.msdn.microsoft.com/KB2006191

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

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

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

 

カスタムの 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 メソッドで、SQL Azure 上に準備しておいたメンバーシップ (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 you get named provider, please use Membership.Provider["<Provider Name>"] )
    MembershipProvider prov = Membership.Provider;
    return prov.ValidateUser(userName, password);
}
. . .

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

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

<?xml version="1.0"?>
<configuration>
  <configSections>
  </configSections>
  . . .

  <connectionStrings>
    <add name="SqlConn"
         connectionString="Data Source=AAAAAAAAAA.database.windows.net;Initial Catalog=aspnetdb;User Id=testuser@AAAAAAAAAA;Password=XXXXXXX"
         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 を使用して構築することもできます。(2012/02 変更 : 「WCF Web Api」は、今後、「ASP.NET Web API」としてリリース予定です。)

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

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

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

 

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

 

 

Leave a Comment
  • Please add 8 and 3 and type the answer here:
  • Post