マネージ コード (C#) を使用して XML データベースに基づく FTP 認証プロバイダーを作成する方法

公開日: 2009 年 7 月 23 日 (作業者: robmcm (英語))

更新日: 2009 年 9 月 9 日 (作業者: robmcm (英語))

マイクロソフトでは、Windows Server® 2008 用に完全に書き換えた新しい FTP サービスを作成しました。この新しい FTP サービスでは多くの新機能が追加され、Web サイト作成者は簡単にコンテンツを発行でき、Web 管理者は多くのセキュリティ オプションと展開オプションを使用できます。この新しい FTP 7.5 サービスでは、FTP サービスに付属の組み込み機能の拡張を可能にする拡張機能がサポートされます。具体的には、FTP 7.5 で、独自の認証プロバイダー、ホーム ディレクトリ プロバイダー、ログ プロバイダーの作成がサポートされます。

このチュートリアルでは、以下のユーザーおよび役割に関するチュートリアルからのサンプル XML ファイルを使用する FTP 認証プロバイダーに対してマネージ コードを使用するステップを説明します。

IIS 7 での読み取り専用サンプル XML メンバーシップおよび役割プロバイダーの使用方法

このチュートリアルの内容

  • ステップ 1: プロジェクト環境のセットアップ
  • ステップ 2: 拡張機能クラスの作成
  • ステップ 3: FTP へのデモ プロバイダーの追加
  • まとめ

必要条件

この記事の手順を完了するには、以下の項目が必須です。

  1. Windows Server 2008 サーバーに IIS 7 がインストールされている必要があります。また、インターネット インフォメーション サービス (IIS) マネージャーもインストールされている必要があります。
  2. 新しい FTP 7.5 サービスがインストールされている必要があります。FTP 7.5 サービスは、Web サイト (https://www.iis.net/(英語)) からダウンロードしてインストールできます。次のいずれかのリンクを使用してください。
  3. サイトで FTP 発行が有効になっている必要があります。
  4. Visual Studio 2008 を使用する必要があります(メモ: Visual Studio の旧バージョンを使用すると、このチュートリアルのステップの一部が不正確になる可能性があります)。

重要

認証要求のパフォーマンスの向上のために、既定では、成功したログインの資格情報が FTP サービスに 15 分間キャッシュされます。つまり、XML ファイル内でパスワードを変更すると、このキャッシュ期間中はその変更が反映されない可能性があります。この問題を解消するために、FTP サービスの資格情報キャッシュを無効にできます。これを実行するには、以下の手順に従ってください。

  1. コマンド プロンプト ウィンドウを開きます。

  2. 次のコマンドを入力します。

    cd /d "%SystemRoot%\System32\Inetsrv"
    Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost
    Net stop FTPSVC
    Net start FTPSVC
    
  3. コマンド プロンプト ウィンドウを閉じます。

ステップ 1: プロジェクト環境のセットアップ

このステップでは、デモ プロバイダー用のプロジェクトを Visual Studio 2008 で作成します。

  1. Microsoft Visual Studio 2008 を開きます。
  2. [ファイル] メニューの [新規作成] をクリックし、[プロジェクト] をクリックします。
  3. [新しいプロジェクト] ダイアログ ボックスで以下の操作を行います。
    • プロジェクトの種類として [Visual C#] を選択します。
    • テンプレートとして [クラス ライブラリ] を選択します。
    • プロジェクト名として「FtpXmlAuthentication」と入力します。
    • [OK] をクリックします。
  4. プロジェクトが開いたら、以下の操作を行って FTP 拡張機能ライブラリへの参照パスを追加します。
    • [プロジェクト] メニューの [FtpXmlAuthentication のプロパティ] をクリックします。
    • [参照パス] タブをクリックします。
    • お使いの Windows バージョン用の FTP 拡張機能アセンブリへのパスを入力します。次は、オペレーティング システム ドライブが C: の場合です。
      • Windows Server 2008 および Windows Vista:
        • C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
      • Windows 7:
        • C:\Program Files\Reference Assemblies\Microsoft\IIS
    • [フォルダの追加] をクリックします。
  5. 厳密な名前のキーをプロジェクトに追加します。
    • [プロジェクト] メニューの [FtpXmlAuthentication のプロパティ] をクリックします。
    • [署名] タブをクリックします。
    • [アセンブリの署名] チェック ボックスをオンにします。
    • 厳密なキー名を選択するドロップダウン ボックスから [<新規作成>] を選択します。
    • キー ファイルの名前として「FtpXmlAuthenticationKey」と入力します。
    • 必要な場合、キー ファイル用のパスワードを入力します。不要な場合は、[キー ファイルをパスワードで保護する] チェック ボックスをオフにします。
    • [OK] をクリックします。
  6. オプション: カスタム ビルド イベントを追加して、開発用コンピューター上のグローバル アセンブリ キャッシュ (GAC) に DLL を自動的に追加できます。
    • [プロジェクト] メニューの [FtpXmlAuthentication のプロパティ] をクリックします。

    • [ビルド イベント] タブをクリックします。

    • [ビルド後に実行するコマンド ライン] ダイアログ ボックスに、次を入力します。

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. プロジェクトを保存します。

ステップ 2: 拡張機能クラスの作成

このステップでは、デモ プロバイダー用のログ拡張機能インターフェイスを実装します。

  1. プロジェクトに FTP 拡張機能ライブラリへの参照を追加します。
    • [プロジェクト] メニューの [参照の追加] をクリックします。
    • [.NET] タブで、[Microsoft.Web.FtpServer] をクリックします。
    • [OK] をクリックします。
  2. プロジェクトに System.Web への参照を追加します。
    • [プロジェクト] メニューの [参照の追加] をクリックします。
    • [.NET] タブで、[System.Web] をクリックします。
    • [OK] をクリックします。
  3. プロジェクトに System.Configuration への参照を追加します。
    • [プロジェクト] メニューの [参照の追加] をクリックします。
    • [.NET] タブで、[System.Configuration] をクリックします。
    • [OK] をクリックします。
  4. 認証クラスのコードを追加します。
    • [ソリューション エクスプローラー] で [Class1.cs] ファイルをダブルクリックします。

    • 既存のコードを削除します。

    • エディターに次のコードを貼り付けます。

      using System;
      using System.Collections;
      using System.Collections.Specialized;
      using System.Collections.Generic;
      using System.Configuration.Provider;
      using System.IO;
      using System.Linq;
      using System.Text;
      using System.Xml;
      using Microsoft.Web.FtpServer;
      using System.Xml.XPath;
      // XML 認証プロバイダー クラスを定義します。
      public class FtpXmlAuthentication :
      BaseProvider,
      IFtpAuthenticationProvider,
      IFtpRoleProvider
      {
      // ユーザー データを格納する XML ファイルへのパスを格納する文字列を作成します。
      private static string _xmlFileName;
      // 変更通知のためのファイル システム監視オブジェクトを作成します。
      private static FileSystemWatcher _xmlFileWatch;
      // ユーザー データを保持するディクショナリを作成します。
      private static Dictionary<string, XmlUserData> _XmlUserData =
      new Dictionary<string, XmlUserData>(
      StringComparer.InvariantCultureIgnoreCase);
      // Initialize メソッドをオーバーライドして構成設定を取得します。
      protected override void Initialize(StringDictionary config)
      {
      // XML ファイルへのパスを取得します。
      _xmlFileName = config["xmlFileName"];
      // パスが空でないかテストします。
      if (string.IsNullOrEmpty(_xmlFileName))
      {
      // パスがないか空である場合に例外をスローします。
      throw new ArgumentException("Missing xmlFileName value in configuration.");
      }
      // ファイルが存在するかテストします。
      if (File.Exists(_xmlFileName) == false)
      {
      // ファイルが存在しない場合に例外をスローします。
      throw new ArgumentException("The specified XML file does not exist.");
      }
      try
      {
      // XML ファイルのためのファイル システム監視オブジェクトを作成します。
      _xmlFileWatch = new FileSystemWatcher();
      // 監視する XML ファイルを含むフォルダーを指定します。
      _xmlFileWatch.Path = _xmlFileName.Substring(0, _xmlFileName.LastIndexOf(@"\"));
      // XML ファイル名に基づいてイベントをフィルター処理します。
      _xmlFileWatch.Filter = _xmlFileName.Substring(_xmlFileName.LastIndexOf(@"\") + 1);
      // 最終書き込み時刻およびファイル サイズに基づいて変更通知をフィルター処理します。
      _xmlFileWatch.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
      // イベント ハンドラーを追加します。
      _xmlFileWatch.Changed += new FileSystemEventHandler(this.XmlFileChanged);
      // 変更通知イベントを有効化します。
      _xmlFileWatch.EnableRaisingEvents = true;
      }
      catch (Exception ex)
      {
      // エラーが発生した場合に例外をスローします。
      throw new ProviderException(ex.Message);
      }
      }
      // XML ファイルへの変更のイベント ハンドラーを定義します。
      public void XmlFileChanged(object sender, FileSystemEventArgs e)
      {
      // 変更されたファイルが XML データ ファイルであることを検証します。
      if (e.Name.Equals(
      _xmlFileName.Substring(_xmlFileName.LastIndexOf(@"\") + 1),
      StringComparison.OrdinalIgnoreCase))
      {
      // 既存のユーザー ディクショナリの内容を消去します。
      _XmlUserData.Clear();
      // ユーザー ディクショナリにデータを入れ直します。
      ReadXmlDataStore();
      }
      }
      // AuthenticateUser メソッドを定義します。
      bool IFtpAuthenticationProvider.AuthenticateUser(
      string sessionId,
      string siteName,
      string userName,
      string userPassword,
      out string canonicalUserName)
      {
      // 正規ユーザー名を定義します。
      canonicalUserName = userName;
      // ユーザー名とパスワードが空でないことを検証します。
      if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(userPassword))
      {
      // どちらかが空の場合に false (認証失敗) を返します。
      return false;
      }
      else
      {
      try
      {
      // XML ファイルからユーザー データや役割データを取得します。
      ReadXmlDataStore();
      // ユーザー オブジェクトを作成します。
      XmlUserData user;
      // ユーザー名がユーザーのディクショナリ内に存在するかテストします。
      if (_XmlUserData.TryGetValue(userName, out user))
      {
      // 大文字と小文字を区別してパスワードを比較します。
      if (String.Compare(user.Password, userPassword, false) == 0)
      {
      // パスワードが一致した場合に true (認証成功) を返します。
      return true;
      }
      }
      }
      catch (Exception ex)
      {
      // エラーが発生した場合に例外をスローします。
      throw new ProviderException(ex.Message);
      }
      }
      // ここまでに認証が失敗した場合に false (認証失敗) を返します。
      return false;
      }
      bool IFtpRoleProvider.IsUserInRole(
      string sessionId,
      string siteName,
      string userName,
      string userRole)
      {
      // ユーザー名と役割名が空でないことを検証します。
      if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(userRole))
      {
      // どちらかが空の場合に false (役割参照失敗) を返します。
      return false;
      }
      else
      {
      try
      {
      // XML ファイルからユーザー データや役割データを取得します。
      ReadXmlDataStore();
      // ユーザー オブジェクトを作成します。
      XmlUserData user;
      // ユーザー名がユーザーのディクショナリ内に存在するかテストします。
      if (_XmlUserData.TryGetValue(userName, out user))
      {
      // ユーザーの役割をループ処理します。
      foreach (string role in user.Roles)
      {
      // 大文字と小文字を区別しないで役割名を比較します。
      if (String.Compare(role, userRole, true) == 0)
      {
      // 役割名が一致した場合に true (役割参照成功) を返します。
      return true;
      }
      }
      }
      }
      catch (Exception ex)
      {
      // エラーが発生した場合に例外をスローします。
      throw new ProviderException(ex.Message);
      }
      }
      // ここまでに役割参照が失敗した場合に false (役割参照失敗) を返します。
      return false;
      }
      // XML ファイルからユーザー データや役割データを取得します。
      private void ReadXmlDataStore()
      {
      // データを取得している間はプロバイダーをロックします。
      lock (this)
      {
      try
      {
      // ディクショナリにデータが既に存在しているかテストします。
      if (_XmlUserData.Count == 0)
      {
      // XML ドキュメント オブジェクトを作成し、データ XML ファイルを読み込みます。
      XPathDocument xmlDocument = new XPathDocument(_xmlFileName);
      // XML ファイルのナビゲーションに使用するナビゲーター オブジェクトを作成します。
      XPathNavigator xmlNavigator = xmlDocument.CreateNavigator();
      // XML ファイル内のユーザーをループ処理します。
      foreach (XPathNavigator node in xmlNavigator.Select("/Users/User"))
      {
      // ユーザー名を取得します。
      string userName = GetInnerText(node, "UserName");
      // ユーザーのパスワードを取得します。
      string password = GetInnerText(node, "Password");
      // データが空でないかテストします。
      if ((String.IsNullOrEmpty(userName) == false) && (String.IsNullOrEmpty(password) == false))
      {
      // ユーザーの役割を取得します。
      string xmlRoles = GetInnerText(node, "Roles");
      // ユーザーの役割用の文字列配列を作成します。
      string[] userRoles = new string[0];
      // ユーザーに役割が定義されているかテストします。
      if (String.IsNullOrEmpty(xmlRoles) == false)
      {
      // 役割をコンマで区切ります。
      userRoles = xmlRoles.Split(',');
      }
      // ユーザー データ クラスを作成します。
      XmlUserData userData = new XmlUserData(password, userRoles);
      // ユーザー データをディクショナリに格納します。
      _XmlUserData.Add(userName, userData);
      }
      }
      }
      }
      catch (Exception ex)
      {
      // エラーが発生した場合に例外をスローします。
      throw new ProviderException(ex.Message);
      }
      }
      }
      // XML 要素からデータを取得します。
      private static string GetInnerText(XPathNavigator xmlNode, string xmlElement)
      {
      string xmlText = "";
      try
      {
      // XML 要素が存在するかテストします。
      if (xmlNode.SelectSingleNode(xmlElement) != null)
      {
      // XML 要素内のテキストを取得します。
      xmlText = xmlNode.SelectSingleNode(xmlElement).Value.ToString();
      }
      }
      catch (Exception ex)
      {
      // エラーが発生した場合に例外をスローします。
      throw new ProviderException(ex.Message);
      }
      // 要素のテキストを返します。
      return xmlText;
      }
      }
      // ユーザー データ クラスを定義します。
      internal class XmlUserData
      {
      // ユーザーのパスワードを保持するプライベート文字列を作成します。
      private string _password = "";
      // ユーザーの役割を保持するプライベート文字列配列を作成します。
      private string[] _roles = new string[0];
      // ユーザーのパスワードおよび役割配列を必要とするクラス コンストラクタを定義します。
      public XmlUserData(string Password,string[] Roles)
      {
      this.Password = Password;
      this.Roles = Roles;
      }
      // パスワード プロパティを定義します。
      public string Password
      {
      get { return _password; }
      set
      {
      try { _password = value; }
      catch (Exception ex)
      {
      throw new ProviderException(ex.Message);
      }
      }
      }
      // 役割プロパティを定義します。
      public string[] Roles
      {
      get { return _roles; }
      set {
      try { _roles = value; }
      catch (Exception ex)
      {
      throw new ProviderException(ex.Message);
      }
      }
      }
      }
      
  5. プロジェクトを保存してビルドします。

メモ: GAC にアセンブリを登録するオプションのステップを使用しなかった場合は、アセンブリを IIS 7 コンピューターに手動でコピーし、Gacutil.exe ツールを使用して GAC にアセンブリを追加する必要があります。詳細については、マイクロソフト MSDN Web サイトの次のトピックを参照してください。

グローバル アセンブリ キャッシュ ツール (Gacutil.exe)

ステップ 3: FTP へのデモ プロバイダーの追加

このステップでは、デモ プロバイダーを FTP サービスおよび既定の Web サイトに追加します。

XML ファイルの追加

メンバーシップ ユーザーおよび役割用に XML ファイルを作成します。

  • テキスト エディターに次のコードを貼り付けます。

    <Users>
    <User>
    <UserName>Alice</UserName>
    <Password>contoso!</Password>
    <EMail>alice@contoso.com</EMail>
    <Roles>Members,Administrators</Roles>
    </User>
    <User>
    <UserName>Bob</UserName>
    <Password>contoso!</Password>
    <EMail>bob@contoso.com</EMail>
    <Roles>Members</Roles>
    </User>
    </Users>
    
  • このコードに「Users.xml」と名前を付けてコンピューターに保存します。たとえば、パス C:\Inetpub\XmlSample\Users.xml を使用できます。

メモ: セキュリティ上の理由から、Web サイトのコンテンツ領域に存在するフォルダーにこのファイルを格納しないようにしてください。

プロバイダーの追加

  1. 拡張機能プロバイダー用のアセンブリ情報を判断します。
    • Windows Explorer で、パス C:\Windows\assembly を開きます。ここで、C: はオペレーティング システム ドライブです。
    • FtpXmlAuthentication アセンブリを探します。
    • アセンブリを右クリックし、[プロパティ] をクリックします。
    • [カルチャ] の値 ([Neutral] など) をコピーします。
    • [バージョン] 番号 ([1.0.0.0] など) をコピーします。
    • [公開キー トークン] の値 ([426f62526f636b73] など) をコピーします。
    • [キャンセル] をクリックします。
  2. 前の手順で得た情報を使用して、FTP プロバイダーのグローバル リストに拡張機能プロバイダーを追加し、このプロバイダーのオプションを構成します。
    • この時点では、カスタム認証モジュールにプロパティを追加するためのユーザー インターフェイスがないので、次のコマンド ラインを使用する必要があります。

      cd %SystemRoot%\System32\Inetsrv
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"[name='FtpXmlAuthentication',type='FtpXmlAuthentication,FtpXmlAuthentication,version=1.0.0.0,Culture=neutral,PublicKeyToken=426f62526f636b73']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpXmlAuthentication']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpXmlAuthentication'].[key='xmlFileName',value='C:\Inetpub\XmlSample\Users.xml']" /commit:apphost
      
    • メモ: **xmlFileName 属性に指定するファイル パスは、このチュートリアルの前の手順でコンピューターに保存した Users.xml ファイルのパスと一致する必要があります。

  3. FTP サイト用のカスタム認証プロバイダーを追加します。
    • インターネット インフォメーション サービス (IIS) マネージャーで FTP サイトを開きます。
    • メイン ウィンドウで [FTP 認証] をダブルクリックします。
    • [操作] ウィンドウで、[カスタム プロバイダー] をクリックします。
    • プロバイダーの一覧で [FtpXmlAuthentication] をオンにします。
    • [OK] をクリックします。
  4. 認証プロバイダー用の承認規則を追加します。
    • メイン ウィンドウで [FTP 承認規則] をダブルクリックします。
    • [操作] ウィンドウで、[許可規則の追加] をクリックします。
    • 以下のどちらかの承認規則を追加できます。
      • 指定されたユーザー:
        • アクセスのオプションで [指定されたユーザー] を選択します。
        • ユーザー名を入力します。たとえば、このチュートリアルの XML サンプルを使用する場合は「Alice」または「Bob」と入力できます。
      • 役割またはグループ:
        • アクセスのオプションで [指定された役割またはユーザー グループ] を選択します。
        • 役割名またはグループ名を入力します。たとえば、このチュートリアルの XML サンプルを使用する場合は「Members」または「Administrators」と入力できます。
      • [アクセス許可] オプションで、[読み取り] や [書き込み] を選択します。
    • [OK] をクリックします。

まとめ

このチュートリアルでは、次について学びました。

  • カスタム FTP 認証プロバイダー用に Visual Studio 2008 でプロジェクトを作成する方法
  • カスタム FTP 認証用の拡張機能インターフェイスの実装方法
  • FTP サービスへのカスタム認証プロバイダーの追加方法

ユーザーが FTP サイトに接続したときに、FTP サービスはカスタム認証プロバイダーを使用してユーザーの認証を試行します。この認証に失敗すると、FTP サービスは他の組み込みプロバイダーまたは認証プロバイダーを使用してユーザーを認証します。

関連コンテンツ

記事