Freigeben über


Erstellen eines FTP-Authentifizierungsproviders mit einer XML-Datenbank

von Robert McMurray

Microsoft hat einen neuen FTP-Dienst entwickelt, der für Windows Server® 2008 komplett neu geschrieben wurde. Dieser neue FTP-Dienst enthält viele neue Funktionen, mit denen Web-Autoren und -Autorinnen Inhalte einfacher als bisher veröffentlichen können, und bietet Web-Admins mehr Sicherheit und Optionen für die Bereitstellung. Der neue FTP 7.5-Dienst unterstützt die Erweiterbarkeit, mit der Sie die in den FTP-Dienst integrierten Funktionen erweitern können. Genauer gesagt, unterstützt FTP 7.5 die Erstellung Ihrer eigenen Authentifizierungs-, Heimverzeichnis- und Protokollierungsanbieter.

Diese exemplarische Vorgehensweise führt Sie durch die Schritte zum Verwenden von verwaltetem Code zu einem FTP-Authentifizierungsanbieter, der die XML-Beispieldatei aus der folgenden exemplarischen Vorgehensweise für Benutzer und Rollen verwendet:

Voraussetzungen

Die folgenden Elemente sind erforderlich, um die Verfahren in diesem Artikel abzuschließen:

  1. IIS 7.0 oder höher muss auf Ihrem Windows Server 2008-Server installiert sein, und der Internetinformationsdienste (IIS)-Manager muss ebenfalls installiert sein.

  2. Der neue FTP 7.5-Dienst muss installiert sein.

  3. Sie müssen die FTP-Veröffentlichung für eine Website aktiviert haben.

  4. Sie müssen Visual Studio 2008 verwenden.

    Hinweis

    Wenn Sie eine frühere Version von Visual Studio verwenden, sind einige der Schritte in dieser Anleitung möglicherweise nicht korrekt.

Wichtig

Um die Leistung für Authentifizierungsanforderungen zu verbessern, speichert der FTP-Dienst die Anmeldeinformationen für erfolgreiche Anmeldungen standardmäßig 15 Minuten zwischen. Dies bedeutet, dass diese Änderung möglicherweise für die Cachedauer nicht angezeigt wird, wenn Sie das Kennwort in Ihrer XML-Datei ändern. Um dies zu verringern, können Sie die Zwischenspeicherung von Anmeldeinformationen für den FTP-Dienst deaktivieren. Führen Sie dazu die folgenden Schritte aus:

  1. Öffnen Sie eine Eingabeaufforderung.

  2. Geben Sie folgende Befehle ein:

    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. Schließen Sie die Eingabeaufforderung.

Schritt 1: Einrichten der Projektumgebung

In diesem Schritt erstellen Sie ein Projekt in Visual Studio 2008 für den Demoanbieter.

  1. Öffnen Sie Microsoft Visual Studio 2008.

  2. Klicken Sie auf das Menü Datei, dann auf Neu und dann auf Projekt.

  3. Im Dialogfeld Neues Projekt:

    • Wählen Sie Visual C# als Projekttyp aus.
    • Wählen Sie Klassenbibliothek als Vorlage aus.
    • Geben Sie FtpXmlAuthentication als Namen des Projekts ein.
    • Klicken Sie auf OK.
  4. Wenn das Projekt geöffnet wird, fügen Sie der FTP-Erweiterbarkeitsbibliothek einen Verweispfad hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf FtpXmlAuthentication-Eigenschaften.

    • Klicken Sie auf die Schaltfläche Verweispfade.

    • Geben Sie den Pfad zur FTP-Erweiterbarkeitsassembly für Ihre Windows-Version ein, wobei „C:“ Ihr Betriebssystemlaufwerk ist.

      -Für Windows Server 2008 und Windows Vista: C:\Windows\assembly\GAC\_MSIL\Microsoft.Web.FtpServer\7.5.0.0\_\_31bf3856ad364e35 -Für Windows 7: C:\Program Files\Reference Assemblies\Microsoft\IIS

    • Klicken Sie auf Ordner hinzufügen.

  5. Fügen Sie dem Projekt einen Schlüssel mit einem starken Namen hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf FtpXmlAuthentication-Eigenschaften.
    • Klicken Sie auf die Registerkarte Signierung.
    • Aktivieren Sie das Kontrollkästchen Assembly signieren.
    • Wählen Sie aus dem Dropdownfeld mit starkem Schlüsselnamen <Neu …> aus.
    • Geben Sie FtpXmlAuthenticationKey für den Schlüsseldateinamen ein.
    • Geben Sie bei Bedarf ein Kennwort für die Schlüsseldatei ein; deaktivieren Sie andernfalls das Kontrollkästchen Meine Schlüsseldatei mit einem Kennwort schützen.
    • Klicken Sie auf OK.
  6. Optional: Sie können ein benutzerdefiniertes Build-Ereignis hinzufügen, um die DLL automatisch dem globalen Assemblycache (GAC) auf Ihrem Entwicklungscomputer hinzuzufügen:

    • Klicken Sie auf Projekt, und klicken Sie dann auf FtpXmlAuthentication-Eigenschaften.

    • Klicken Sie auf die Registerkarte Build-Ereignisse.

    • Geben Sie im Dialogfeld Befehlszeile für Post-Build-Ereignis Folgendes ein:

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Speichern Sie das Projekt.

Schritt 2: Erstellen der Erweiterbarkeitsklasse

In diesem Schritt implementieren Sie die Erweiterbarkeitsschnittstelle der Protokollierung für den Demoanbieter.

  1. Fügen Sie einen Verweis auf die FTP-Erweiterbarkeitsbibliothek für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen.
    • Klicken Sie auf der Registerkarte .NET auf Microsoft.Web.FtpServer.
    • Klicken Sie auf OK.
  2. Fügen Sie einen Verweis auf System.Web für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen.
    • Klicken Sie auf der Registerkarte .NET auf System.Web.
    • Klicken Sie auf OK.
  3. Fügen Sie einen Verweis auf System.Configuration für das Projekt hinzu:

    • Klicken Sie auf Projekt, und klicken Sie dann auf Verweis hinzufügen.
    • Klicken Sie auf der Registerkarte .NET auf System.Configuration.
    • Klicken Sie auf OK.
  4. Fügen Sie den Code für die Authentifizierungsklasse hinzu:

    • Doppelklicken Sie im Projektmappen-Explorer auf die Datei Class1.cs.

    • Entfernen Sie den vorhandenen Code.

    • Fügen Sie den folgenden Code in den Editor ein:

      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. Speichern und kompilieren Sie das Projekt.

Hinweis

Wenn Sie die optionalen Schritte zur Registrierung der Assemblys im GAC nicht verwendet haben, müssen Sie die Assemblys manuell auf Ihren IIS-Computer kopieren und die Assemblys mit dem Tool Gacutil.exe zum GAC hinzufügen. Weitere Informationen finden Sie unter Gacutil.exe (Global Assembly Cache-Tool).

Schritt 3: Hinzufügen des Demoanbieters zu FTP

In diesem Schritt fügen Sie den Demoanbieter zu Ihrem FTP-Dienst und der Standardwebsite hinzu.

Hinzufügen der XML-Datei

Erstellen Sie eine XML-Datei für die Mitgliedschaftsbenutzer und -rollen:

  • Fügen Sie den folgenden Code in einem Texteditor ein:

    <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>
    
  • Speichern Sie den Code als „Users.xml“ auf Ihrem Computer. Sie können z. B. den Pfad C:\Inetpub\XmlSample\Users.xml verwenden.

Hinweis

Aus Sicherheitsgründen sollte diese Datei nicht in einem Ordner gespeichert werden, der sich im Inhaltsbereich Ihrer Website befindet.

Hinzufügen des Anbieters

  1. Ermitteln Sie die Assembly-Informationen für den Erweiterbarkeitsanbieter:

    • Öffnen Sie im Windows-Explorer Ihren Pfad C:\Windows\assembly, wobei „C:“ Ihr Betriebssystemlaufwerk ist.
    • Suchen Sie die FtpXmlAuthentication-Assembly.
    • Klicken Sie mit der rechten Maustaste auf die Assembly und klicken Sie dann auf Eigenschaften.
    • Kopieren Sie den Wert Culture, z. B. Neutral.
    • Kopieren Sie die Version-Nummer, z. B. 1.0.0.0.
    • Kopieren Sie den Wert Public Key Token, z. B. 426f62526f636b73.
    • Klicken Sie auf Abbrechen.
  2. Fügen Sie mithilfe der Informationen aus den vorherigen Schritten den Erweiterungsanbieter zur globalen Liste der FTP-Anbieter hinzu, und konfigurieren Sie die Optionen für den Anbieter:

    • Derzeit gibt es keine Benutzeroberfläche, über die Sie Eigenschaften für ein benutzerdefiniertes Authentifizierungsmodul hinzufügen können, sodass Sie die folgende Befehlszeile verwenden müssen:

      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
      

    Hinweis

    Der im xmlFileName-Attribut angegebene Dateipfad muss mit dem Pfad übereinstimmen, in dem Sie die Datei "Users.xml" auf Ihrem Computer weiter oben in dieser exemplarischen Vorgehensweise gespeichert haben.

  3. Fügen Sie den benutzerdefinierten Authentifizierungsanbieter für eine FTP-Site hinzu:

    • Öffnen Sie eine FTP-Website im IIS-Manager (Internet Information Services).
    • Doppelklicken Sie im Hauptfenster auf die FTP-Authentifizierung.
    • Klicken Sie auf Benutzerdefinierte Anbieter (im Bereich Aktionen).
    • Überprüfen Sie FtpXmlAuthentication in der Anbieterliste.
    • Klicken Sie auf OK.
  4. Hinzufügen einer Autorisierungsregel für den Authentifizierungsanbieter:

    • Doppelklicken Sie im Hauptfenster auf FTP-Autorisierungsregeln.

    • Klicken Sie im Bereich Aktionen auf Zulassungsregel hinzufügen....

    • Sie können eine der folgenden Autorisierungsregeln hinzufügen:

      -Für einen bestimmten Benutzer:

      • Wählen Sie Angegebene Benutzer für die Zugriffsoption aus.
      • Geben Sie den Benutzernamen ein. Wenn Sie beispielsweise das XML-Beispiel in dieser exemplarischen Vorgehensweise verwenden, können Sie „Alice“ oder „Bob“ eingeben.

      -Für eine Rolle oder Gruppe:

      • Wählen Sie Angegebene Rollen oder Benutzergruppen für die Zugriffsoption aus.
      • Geben Sie den Rollen- oder Gruppennamen ein. Wenn Sie beispielsweise das XML-Beispiel in dieser exemplarischen Vorgehensweise verwenden, können Sie „Mitglieder“ oder „Administratoren“ eingeben.

      -Wählen Sie Lesen und/oder Schreiben für die Option Berechtigungen aus.

    • Klicken Sie auf OK.

Zusammenfassung

In dieser Anleitung haben Sie folgendes gelernt:

  • Erstellen eines Projekts in Visual Studio 2008 für einen benutzerdefinierten FTP-Authentifizierungsanbieter.
  • Implementieren Sie die Erweiterbarkeitsschnittstelle für die benutzerdefinierte FTP-Authentifizierung.
  • Fügen Sie Ihrem FTP-Dienst einen benutzerdefinierten Authentifizierungsanbieter hinzu.

Wenn Benutzer eine Verbindung mit Ihrem FTP-Standort herstellen, versucht der FTP-Dienst, Benutzer bei Ihrem benutzerdefinierten Authentifizierungsanbieter zu authentifizieren. Wenn dies fehlschlägt, verwendet der FTP-Dienst andere integrierte oder Authentifizierungsanbieter, um Benutzer zu authentifizieren.