作成者: Robert McMurray
[このドキュメントは暫定的であり、変更される可能性があります。]
互換性
バージョン | メモ |
---|---|
IIS 8.0 | カスタム承認には FTP 8.0 サービスが必要です。 |
IIS 7.5 | FTP 7.5 では、カスタム承認はサポートされていません。 |
IIS 7.0 | FTP 7.0 では、カスタム承認はサポートされていません。 |
注: FTP 8.0 サービスは、Windows 8 および Windows 8 Server の IIS 8.0 の機能として付属しています。
はじめに
Microsoft は、FTP 7.0 および FTP 7.5 で導入された豊富な機能セットに基づいて構築された、Windows Server® 2012 用の新しい FTP 8.0 サービスを作成しました。 さらに、この新しい FTP サービスは、カスタム承認のサポートや単純なイベント処理などの新しい拡張機能を使用して、FTP 用のアプリケーション プログラミング インターフェイス (API) の一覧を拡張します。
このチュートリアルでは、ユーザーとロールに対して次のチュートリアルのサンプル XML ファイルを使用する FTP 認証および承認プロバイダーにマネージド コードを使用する手順と、カスタム承認規則をサポートするために必要な変更について説明します。
前提条件
この記事の手順を完了するには、次の項目が必要です。
IIS 8 を Windows Server 8 サーバーにインストールし、インターネット インフォメーション サービス (IIS) マネージャーもインストールする必要があります。
FTP 8 サービスをインストールする必要があります。
サイトに対して FTP 発行を有効にする必要があります。
Visual Studio 2008 以降を使用する必要があります。
Note
別のバージョンの Visual Studio も使用できますが、このチュートリアルの手順の一部が異なる場合があります。
重要
認証要求のパフォーマンスを向上させるために、FTP サービスは、成功したログインの資格情報を既定で 15 分間キャッシュします。 つまり、XML ファイルのパスワードを変更した場合、この変更がキャッシュの存続期間中反映されない可能性があります。 これを軽減するために、FTP サービスの資格情報キャッシュを無効にすることができます。 そのためには、次の手順を行ってください。
コマンド プロンプトを開きます。
次のコマンドを入力します。
cd /d "%SystemRoot%\System32\Inetsrv" Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost Net stop FTPSVC Net start FTPSVC
コマンド プロンプトを閉じます。
手順 1: プロジェクト環境を設定する
この手順では、Visual Studio でデモ プロバイダー用のプロジェクトを作成します。
Microsoft Visual Studio 2008 以降 を開きます。
[ファイル] メニューの [新規作成]、[プロジェクト] の順にクリックします。
新しいプロジェクト ダイアログ ボックスで以下を実行します。
- プロジェクトの種類として [Visual C#] を選択します。
- テンプレートとして [クラス ライブラリ] を選択します。
- プロジェクトの名前として「FtpXmlAuthorization」と入力します。
- OK をクリックします。
プロジェクトが開いたら、FTP 機能拡張ライブラリへの参照パスを追加します。
[プロジェクト] をクリックし、[FtpXmlAuthorization Properties] (FtpXmlAuthorization プロパティ) をクリックします。
[参照パス] タブをクリックします。
使用している Windows のバージョンの FTP 拡張機能アセンブリへのパスを入力します。C: はオペレーティング システム ドライブです。
C:\Program Files\Reference Assemblies\Microsoft\IIS
[フォルダーの追加] をクリックします。
プロジェクトに厳密な名前のキーを追加します。
- [プロジェクト] をクリックし、[FtpXmlAuthorization Properties] (FtpXmlAuthorization プロパティ) をクリックします。
- [署名] タブをクリックします。
- [アセンブリの署名] チェック ボックスをオンにします。
- 厳密なキー名のドロップダウン ボックスから <[新規...]> を選択します。
- キー ファイル名として「FtpXmlAuthorizationKey」と入力します。
- 必要に応じて、キー ファイルのパスワードを入力します。それ以外の場合は、[Protect my key file with a password] (パスワードでキー ファイルを保護する) チェック ボックスをオフにします。
- OK をクリックします。
省略可能: カスタム ビルド イベントを追加して、開発用コンピューターのグローバル アセンブリ キャッシュ (GAC) に DLL を自動的に追加できます。
[プロジェクト] をクリックし、[FtpXmlAuthorization Properties] (FtpXmlAuthorization プロパティ) をクリックします。
[ビルド イベント] タブをクリックします。
[ビルド後に実行するコマンド ライン] ダイアログ ボックスに、次のように入力します。
net stop ftpsvc call "%VS90COMNTOOLS%\vsvars32.bat">null gacutil.exe /if "$(TargetPath)" net start ftpsvc
プロジェクトを [保存] します。
手順 2: 機能拡張クラスを作成する
この手順では、デモ プロバイダーのログ拡張インターフェイスを実装します。
プロジェクトの FTP 拡張機能ライブラリへの参照を追加します。
- [プロジェクト] をクリックし、[参照の追加] をクリックします。
- [.NET] タブで、[Microsoft.Web.FtpServer] をクリックします。
- OK をクリックします。
プロジェクトの System.Web への参照を追加します。
- [プロジェクト] をクリックし、[参照の追加] をクリックします。
- [.NET] タブで、[System.Web] をクリックします。
- OK をクリックします。
プロジェクトの System.Configuration への参照を追加します。
- [プロジェクト] をクリックし、[参照の追加] をクリックします。
- [.NET] タブで、[System.Configuration] をクリックします。
- OK をクリックします。
認証と承認クラスのコードを追加します。
ソリューション エクスプローラーで、[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 and authorization provider class. public class FtpXmlAuthorization : BaseProvider, IFtpAuthenticationProvider, IFtpAuthorizationProvider, 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; } // Define the IsUserInRole method. 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; } // Define the AuthorizeUser method. FtpAccess IFtpAuthorizationProvider.GetUserAccessPermission( string pszSessionId, string pszSiteName, string pszVirtualPath, string pszUserName) { // Define the default access. FtpAccess _ftpAccess = FtpAccess.None; // Validate that the user and virtual path are not empty. if (String.IsNullOrEmpty(pszUserName) || String.IsNullOrEmpty(pszVirtualPath)) { // Return false (authorization failed) if either are empty. return FtpAccess.None; } 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(pszUserName, out user)) { // Loop through the user's roles. foreach (KeyValuePair<string, FtpAccess> rule in user.Rules) { // Test if the virtual path matches an authorization rule. // Note: This is a very simple path search. if (pszVirtualPath.StartsWith(rule.Key)) { _ftpAccess = rule.Value; } } } } catch (Exception ex) { // Raise an exception if an error occurs. throw new ProviderException(ex.Message); } } return _ftpAccess; } // 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 dictionary to hold the user's authorization rules. Dictionary<string, FtpAccess> userRules = new Dictionary<string, FtpAccess>( StringComparer.InvariantCultureIgnoreCase); // Loop through the set of authorization rules for the user. foreach (XPathNavigator rule in node.Select("Rules/Rule")) { // Retrieve the URL path for the authorization rule. string xmlPath = rule.GetAttribute("path", string.Empty); // Strip trailing slashes from paths. if (xmlPath.EndsWith("/") && xmlPath.Length > 1) xmlPath = xmlPath.Substring(0, xmlPath.Length - 1); // Retrieve the user's permissionsfor the authorization rule. string xmlPermissions = rule.GetAttribute("permissions", string.Empty); // Parse the FTP access permissions for the authorization rule. FtpAccess userPermissions = FtpAccess.None; switch (xmlPermissions.Replace(" ", "").ToLower()) { case "read": userPermissions = FtpAccess.Read; break; case "write": userPermissions = FtpAccess.Write; break; case "read,write": case "write,read": userPermissions = FtpAccess.ReadWrite; break; default: userPermissions = FtpAccess.None; break; } // Add the authorization rule to the dictionary. userRules.Add(xmlPath, userPermissions); } // Create a user data class. XmlUserData userData = new XmlUserData(password, userRoles, userRules); // 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]; // Create a private dictionary to hold user authorization rules. private Dictionary<string, FtpAccess> _rules = new Dictionary<string, FtpAccess>( StringComparer.InvariantCultureIgnoreCase); // Define the class constructor requiring a user's password and roles array. public XmlUserData(string Password, string[] Roles, Dictionary<string, FtpAccess> Rules) { this.Password = Password; this.Roles = Roles; this.Rules = Rules; } // 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); } } } // Define the rules property. public Dictionary<string, FtpAccess> Rules { get { return _rules; } set { try { _rules = value; } catch (Exception ex) { throw new ProviderException(ex.Message); } } } }
プロジェクトを保存してコンパイルします。
Note
オプションの手順を使用してアセンブリを GAC に登録しなかった場合は、手動でアセンブリを IIS コンピューターにコピーし、Gacutil.exe ツールを使用してアセンブリを GAC に追加する必要があります。 詳細については、「Gacutil.exe (グローバル アセンブリ キャッシュ ツール)」を参照してください。
手順 3: FTP へのデモ プロバイダーの追加
この手順では、FTP サービスと既定の Web サイトにデモ プロバイダーを追加します。
XML ファイルの追加
メンバーシップ ユーザーとロールの XML ファイルを作成します。
テキスト エディターに次のコードを貼り付けます。
<Users> <User> <UserName>Alice</UserName> <Password>P@ssw0rd</Password> <EMail>alice@contoso.com</EMail> <Roles>Members,Administrators</Roles> <Rules> <Rule path="/" permissions="Read,Write" /> </Rules> </User> <User> <UserName>Bob</UserName> <Password>P@ssw0rd</Password> <EMail>bob@contoso.com</EMail> <Roles>Members</Roles> <Rules> <Rule path="/" permissions="Read" /> <Rule path="/bob" permissions="Read,Write" /> </Rules> </User> <User> <UserName>Carol</UserName> <Password>P@ssw0rd</Password> <EMail>carol@contoso.com</EMail> <Roles>Members</Roles> <Rules> <Rule path="/" permissions="Write" /> <Rule path="/carol" permissions="Read,Write" /> </Rules> </User> </Users>
コードを "Users.xml" としてコンピューターに保存します。 たとえば、パス
C:\Inetpub\XmlSample\Users.xml
を使用できます。
Note
セキュリティ上の理由から、このファイルは、Web サイトのコンテンツ領域にあるフォルダーに格納しないでください。
プロバイダーの追加
機能拡張プロバイダーのアセンブリ情報を確認します。
- Windows エクスプローラーで
C:\Windows\assembly
パスを開きます。C: はオペレーティング システム ドライブです。 - FtpXmlAuthorization アセンブリを見つけます。
- アセンブリを右クリックし、次に [プロパティ] をクリックします。
- カルチャ値 (例: Neutral) をコピーします。
- バージョン番号 (例: 1.0.0.0) をコピーします。
- Public Key Token 値 (例: 426f62526f636b73) をコピーします。
- [キャンセル] をクリックします。
- Windows エクスプローラーで
前の手順の情報を使用して、拡張プロバイダーを FTP プロバイダーのグローバル リストに追加し、プロバイダーのオプションを構成します。
現時点では、カスタム認証/承認モジュールのプロパティを追加できるユーザー インターフェイスがないため、次のコマンド ラインを使用する必要があります。
cd %SystemRoot%\System32\Inetsrv appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"[name='FtpXmlAuthorization',type='FtpXmlAuthorization,FtpXmlAuthorization,version=1.0.0.0,Culture=neutral,PublicKeyToken=426f62526f636b73']" /commit:apphost appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpXmlAuthorization']" /commit:apphost appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpXmlAuthorization'].[key='xmlFileName',value='C:\Inetpub\XmlSample\Users.xml']" /commit:apphost
Note
xmlFileName 属性で指定するファイル パスは、このチュートリアルの前半でコンピューターに "Users.xml" ファイルを保存したパスと一致する必要があります。
次のように、FTP サイトに対してカスタム認証プロバイダーを指定します。
- インターネット インフォメーション サービス (IIS) マネージャーで FTP サイトを開きます。
- メイン ウィンドウで [FTP Authentication] をダブルクリックします。
- [操作] ペインで [カスタム プロバイダー] をクリックします。
- プロバイダーの一覧で FtpXmlAuthorization を確認します。
- OK をクリックします。
次のように、FTP サイトに対してカスタム承認プロバイダーを指定します。
- インターネット インフォメーション サービス (IIS) マネージャーで FTP サイトを開きます。
- メイン ウィンドウで [FTP Authorization Rules] (FTP の承認規則) をダブルクリックします。
- [操作] ウィンドウで、[機能設定の編集] を選択します。
- [Choose a custom authorization provider] (カスタム承認プロバイダーの選択) をクリックします。
- ドロップダウン メニューで [FtpXmlAuthorization] を選択します。
- OK をクリックします。
まとめ
このチュートリアルでは、次の方法を学習しました。
- カスタム FTP 認証および承認プロバイダー用のプロジェクトを Visual Studio で作成する。
- カスタム FTP 認証および承認の機能拡張インターフェイスを実装する。
- カスタム認証および承認プロバイダーを FTP サービスに追加する。
ユーザーが FTP サイトに接続すると、FTP サービスではカスタム認証プロバイダーを使用してユーザーの認証を試みます。 これが失敗した場合、FTP サービスでは他の組み込みまたは認証プロバイダーを使用してユーザーを認証します。 ユーザーが認証されると、FTP サービスはカスタム承認プロバイダーを呼び出してユーザーを承認します。 これが成功した場合、ユーザーにはサーバーへのアクセス権が付与されます。 失敗した場合、ユーザーはサーバーにアクセスできなくなります。