次の方法で共有


XML データベースを使用して FTP 認証プロバイダーを作成する

作成者: Robert McMurray

Microsoft は、Windows Server® 2008 用に完全に書き換えられた新しい FTP サービスを作成しました。 この新しい FTP サービスには、Web 作成者が以前よりも簡単にコンテンツを公開できる多くの新機能が組み込まれており、Web 管理者により多くのセキュリティとデプロイのオプションが提供されます。 新しい FTP 7.5 サービスでは、FTP サービスに含まれる組み込み機能を拡張できる拡張性がサポートされています。 具体的には、FTP 7.5 では、独自の認証プロバイダー、ホーム ディレクトリ プロバイダー、およびログ プロバイダーの作成がサポートされています。

このチュートリアルでは、ユーザーとロールに対する次のチュートリアルのサンプル XML ファイルを使用する FTP 認証プロバイダーにマネージド コードを使用する手順について説明します。

前提条件

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

  1. IIS 7.0 以降を Windows Server 2008 サーバーにインストールし、インターネット インフォメーション サービス (IIS) マネージャーもインストールする必要があります。

  2. 新しい FTP 7.5 サービスをインストールする必要があります。

  3. サイトに対して FTP 発行を有効にする必要があります。

  4. Visual Studio 2008 を使用する必要があります。

    Note

    以前のバージョンの 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;
      
      // Define the XML authentication provider class.
      public class FtpXmlAuthentication :
        BaseProvider,
        IFtpAuthenticationProvider,
        IFtpRoleProvider
      {
        // Create a string to store the path to the XML file that stores the user data.
        private static string _xmlFileName;
      
        // Create a file system watcher object for change notifications.
        private static FileSystemWatcher _xmlFileWatch;
      
        // Create a dictionary to hold user data.
        private static Dictionary<string, XmlUserData> _XmlUserData =
          new Dictionary<string, XmlUserData>(
            StringComparer.InvariantCultureIgnoreCase);
      
        // Override the Initialize method to retrieve the configuration settings.
        protected override void Initialize(StringDictionary config)
        {
          // Retrieve the path to the XML file.
          _xmlFileName = config["xmlFileName"];
      
          // Test if the path is empty.
          if (string.IsNullOrEmpty(_xmlFileName))
          {
            // Throw an exception if the path is missing or empty.
            throw new ArgumentException("Missing xmlFileName value in configuration.");
          }
      
          // Test if the file exists.
          if (File.Exists(_xmlFileName) == false)
          {
            // Throw an exception if the file does not exist.
            throw new ArgumentException("The specified XML file does not exist.");
          }
      
          try
          {
            // Create a file system watcher object for the XML file.
            _xmlFileWatch = new FileSystemWatcher();
            // Specify the folder that contains the XML file to watch.
            _xmlFileWatch.Path = _xmlFileName.Substring(0, _xmlFileName.LastIndexOf(@"\"));
            // Filter events based on the XML file name.
            _xmlFileWatch.Filter = _xmlFileName.Substring(_xmlFileName.LastIndexOf(@"\") + 1);
            // Filter change notifications based on last write time and file size.
            _xmlFileWatch.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
            // Add the event handler.
            _xmlFileWatch.Changed += new FileSystemEventHandler(this.XmlFileChanged);
            // Enable change notification events.
            _xmlFileWatch.EnableRaisingEvents = true;
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
         }
      
        // Define the event handler for changes to the XML file.
        public void XmlFileChanged(object sender, FileSystemEventArgs e)
        {
          // Verify that the changed file is the XML data file.
          if (e.Name.Equals(
            _xmlFileName.Substring(_xmlFileName.LastIndexOf(@"\") + 1),
            StringComparison.OrdinalIgnoreCase))
          {
            // Clear the contents of the existing user dictionary.
            _XmlUserData.Clear();
            // Repopulate the user dictionary.
            ReadXmlDataStore();
          }
        }
      
        // Define the AuthenticateUser method.
        bool IFtpAuthenticationProvider.AuthenticateUser(
               string sessionId,
               string siteName,
               string userName,
               string userPassword,
               out string canonicalUserName)
        {
          // Define the canonical user name.
          canonicalUserName = userName;
      
          // Validate that the user name and password are not empty.
          if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(userPassword))
          {
            // Return false (authentication failed) if either are empty.
            return false;
          }
          else
          {
            try
            {
              // Retrieve the user/role data from the XML file.
              ReadXmlDataStore();
              // Create a user object.
              XmlUserData user;
              // Test if the user name is in the dictionary of users.
              if (_XmlUserData.TryGetValue(userName, out user))
              {
                // Perform a case-sensitive comparison on the password.
                if (String.Compare(user.Password, userPassword, false) == 0)
                {
                  // Return true (authentication succeeded) if the passwords match.
                  return true;
                }
              }
            }
            catch (Exception ex)
            {
              // Raise an exception if an error occurs.
              throw new ProviderException(ex.Message);
            }
          }
          // Return false (authentication failed) if authentication fails to this point.
          return false;
        }
      
        bool IFtpRoleProvider.IsUserInRole(
             string sessionId,
             string siteName,
             string userName,
             string userRole)
        {
          // Validate that the user and role names are not empty.
          if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(userRole))
          {
            // Return false (role lookup failed) if either are empty.
            return false;
          }
          else
          {
            try
            {
              // Retrieve the user/role data from the XML file.
              ReadXmlDataStore();
              // Create a user object.
              XmlUserData user;
              // Test if the user name is in the dictionary of users.
              if (_XmlUserData.TryGetValue(userName, out user))
              {
                // Loop through the user's roles.
                foreach (string role in user.Roles)
                {
                  // Perform a case-insensitive comparison on the role name.
                  if (String.Compare(role, userRole, true) == 0)
                  {
                    // Return true (role lookup succeeded) if the role names match.
                    return true;
                  }
                }
              }
            }
            catch (Exception ex)
            {
              // Raise an exception if an error occurs.
              throw new ProviderException(ex.Message);
            }
          }
          // Return false (role lookup failed) if role lookup fails to this point.
          return false;
        }
      
        // Retrieve the user/role data from the XML file.
        private void ReadXmlDataStore()
        {
          // Lock the provider while the data is retrieved.
          lock (this)
          {
            try
            {
              // Test if the dictionary already has data.
              if (_XmlUserData.Count == 0)
              {
                // Create an XML document object and load the data XML file
                XPathDocument xmlDocument = new XPathDocument(_xmlFileName);
                // Create a navigator object to navigate through the XML file.
                XPathNavigator xmlNavigator = xmlDocument.CreateNavigator();
                // Loop through the users in the XML file.
                foreach (XPathNavigator node in xmlNavigator.Select("/Users/User"))
                {
                  // Retrieve a user name.
                  string userName = GetInnerText(node, "UserName");
                  // Retrieve the user's password.
                  string password = GetInnerText(node, "Password");
                  // Test if the data is empty.
                  if ((String.IsNullOrEmpty(userName) == false) && (String.IsNullOrEmpty(password) == false))
                  {
                    // Retrieve the user's roles.
                    string xmlRoles = GetInnerText(node, "Roles");
                    // Create a string array for the user roles.
                    string[] userRoles = new string[0];
                    // Test if the user has any roles defined.
                    if (String.IsNullOrEmpty(xmlRoles) == false)
                    {
                      // Split the roles by comma.
                      userRoles = xmlRoles.Split(',');
                    }
                    // Create a user data class.
                    XmlUserData userData = new XmlUserData(password, userRoles);
                    // Store the user data in the dictionary.
                    _XmlUserData.Add(userName, userData);
                  }
                }
              }
            }
            catch (Exception ex)
            {
              // Raise an exception if an error occurs.
              throw new ProviderException(ex.Message);
            }
          }
        }
      
        // Retrieve data from an XML element.
        private static string GetInnerText(XPathNavigator xmlNode, string xmlElement)
        {
          string xmlText = "";
          try
          {
            // Test if the XML element exists.
            if (xmlNode.SelectSingleNode(xmlElement) != null)
            {
              // Retrieve the text in the XML element.
              xmlText = xmlNode.SelectSingleNode(xmlElement).Value.ToString();
            }
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
          // Return the element text.
          return xmlText;
        }  
      }
      
      // Define the user data class.
      internal class XmlUserData
      {
        // Create a private string to hold a user's password.
        private string _password = "";
        // Create a private string array to hold a user's roles.
        private string[] _roles = new string[0];
      
        // Define the class constructor  requiring a user's password and roles array.
        public XmlUserData(string Password,string[] Roles)
        {
          this.Password = Password;
          this.Roles = Roles;
        }
      
        // Define the password property.
        public string Password
        {
          get { return _password; }
          set
          {
            try { _password = value; }
            catch (Exception ex)
            {
              throw new ProviderException(ex.Message);
            }
          }
        }
      
        // Define the roles property.
        public string[] Roles
        {
          get { return _roles; }
          set {
            try { _roles = value; }
            catch (Exception ex)
            {
              throw new ProviderException(ex.Message);
            }
          }
        }
      }
      
  5. プロジェクトを保存してコンパイルします。

Note

オプションの手順を使用してアセンブリを GAC に登録しなかった場合は、手動でアセンブリを IIS コンピューターにコピーし、Gacutil.exe ツールを使用してアセンブリを GAC に追加する必要があります。 詳細については、「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 を使用できます。

Note

セキュリティ上の理由から、このファイルは、Web サイトのコンテンツ領域にあるフォルダーに格納しないでください。

プロバイダーの追加

  1. 機能拡張プロバイダーのアセンブリ情報を確認します。

    • Windows エクスプローラーで C:\Windows\assembly パスを開きます。C: はオペレーティング システム ドライブです。
    • FtpXmlAuthentication アセンブリを見つけます。
    • アセンブリを右クリックし、[プロパティ] を選択します。
    • カルチャ値 (例: Neutral) をコピーします。
    • バージョン番号 (例: 1.0.0.0) をコピーします。
    • Public Key Token 値 (例: 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
      

    Note

    xmlFileName 属性で指定するファイル パスは、このチュートリアルの前半でコンピューターに "Users.xml" ファイルを保存したパスと一致する必要があります。

  3. FTP サイトのカスタム認証プロバイダーを追加します。

    • インターネット インフォメーション サービス (IIS) マネージャーで FTP サイトを開きます。
    • メイン ウィンドウで [FTP Authentication] をダブルクリックします。
    • [操作] ウィンドウで [カスタム プロバイダー] をクリックします。
    • プロバイダーの一覧で FtpXmlAuthentication を確認します。
    • OK をクリックします。
  4. 認証プロバイダーの認可規則を追加します。

    • メイン ウィンドウで [FTP Authorization Rules] (FTP の認可規則) をダブルクリックします。

    • [操作] ウィンドウで [許可ルールの追加...] をクリックします。

    • 次のいずれかの承認規則を追加できます。

      -特定のユーザーの場合:

      • アクセス オプションに [指定されたユーザー] を選択します。
      • ユーザー名を入力します。 たとえば、このチュートリアルの XML サンプルを使用して、「Alice」または「Bob」と入力できます。

      -役割またはグループの場合:

      • アクセス オプションに [指定された役割またはユーザー グループ] を選択します。
      • 役割またはグループの名前を入力します。 たとえば、このチュートリアルの XML サンプルを使用して、「Members」または「Administrators」と入力できます。

      -[アクセス許可] オプションの [読み取り][書き込み] の一方または両方を選択します。

    • OK をクリックします。

まとめ

このチュートリアルでは、次の方法を学習しました。

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

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