Compartilhar via


Criar Provedor de Autenticação FTP usando um Banco de dados XML

por Robert McMurray

A Microsoft criou um novo serviço FTP que foi completamente reescrito para o Windows Server® 2008. Esse novo serviço FTP incorpora muitos novos recursos que permitem que os autores da Web publiquem conteúdo com mais facilidade do que antes e oferece aos administradores da Web mais opções de segurança e implantação. O novo serviço FTP 7.5 dá suporte à extensibilidade que permite a você estender a funcionalidade interna incluída no serviço FTP. Mais especificamente, o FTP 7.5 dá suporte à criação de sua própria autenticação, diretório base e provedores de log.

Este passo a passo guiará você pelas etapas para usar código gerenciado para um provedor de autenticação FTP que usa a amostra de arquivo XML do seguinte passo a passo para usuários e funções:

Pré-requisitos

Os seguintes itens são necessários para concluir os procedimentos nessa seção:

  1. O IIS 7.0 ou superior deve ser instalado no Windows Server 2008 e o Gerenciador do IIS (Serviços de Informações da Internet) também deve ser instalado.

  2. O novo serviço do FTP 7.5 deve ser instalado.

  3. Para um site, a publicação FTP deve ser habilitada.

  4. Você deve usar o Visual Studio 2008.

    Observação

    Se você usar uma versão anterior do Visual Studio, algumas das etapas neste passo a passo poderão não estar corretas.

Importante

Para ajudar a melhorar o desempenho das solicitações de autenticação, o serviço FTP armazena em cache as credenciais para logons bem-sucedidos, por padrão, por 15 minutos. Isso significa que, se você alterar a senha no arquivo XML, essa alteração pode não ser refletida pela duração do cache. Para contornar isso, você pode desabilitar o cache de credenciais para o serviço FTP. Para fazer isso, execute as seguintes etapas:

  1. Abra um prompt de comando.

  2. Digite os seguintes comandos:

    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. Feche o prompt de comando.

Etapa 1: configurar o ambiente do projeto

Nesta etapa, você vai criar um projeto no Visual Studio 2008 para o provedor de demonstração.

  1. Abra o Microsoft Visual Studio 2008.

  2. Selecione o menu Arquivo, em Novo e em Projeto.

  3. Na caixa de diálogo Novo Projeto:

    • Escolha Visual C# como o tipo de projeto.
    • Escolha Biblioteca de classes como o modelo.
    • Digite FtpXmlAuthentication como o nome do projeto.
    • Clique em OK.
  4. Quando o projeto for aberto, adicione um caminho de referência à biblioteca de extensibilidade FTP:

    • Clique em Projeto e, em seguida, clique em Propriedades de FtpXmlAuthentication.

    • Clique na guia Caminhos de referência.

    • Insira o caminho para o assembly de extensibilidade FTP da sua versão do Windows, em que C: é a unidade do sistema operacional.

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

    • Clique em Adicionar pasta.

  5. Adicione uma chave de nome forte ao projeto:

    • Clique em Projeto e, em seguida, clique em Propriedades de FtpXmlAuthentication.
    • Clique na guia Assinatura .
    • Marque a caixa de seleção Assinar o assembly.
    • Escolha <Novo...> na caixa suspensa de nome de chave forte.
    • Digite FtpXmlAuthenticationKey como o nome do arquivo de chave.
    • Se desejar, insira uma senha para o arquivo de chave; caso contrário, desmarque a caixa de seleção Proteger meu arquivo de chave com uma senha.
    • Clique em OK.
  6. Opcional: você pode adicionar um evento de build personalizado para adicionar a DLL automaticamente ao GAC (Cache de Assembly Global) em seu computador de desenvolvimento:

    • Clique em Projeto e, em seguida, clique em Propriedades de FtpXmlAuthentication.

    • Selecione a guia Eventos de build.

    • Insira o seguinte na caixa de diálogo Linha de Comando do evento pós-build:

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Salvaro projeto.

Etapa 2: criar a classe de extensibilidade

Nesta etapa, você vai implementar a interface de extensibilidade de log para o provedor de demonstração.

  1. Adicione uma referência à biblioteca de extensibilidade FTP para o projeto:

    • Clique em Projeto e clique em Adicionar referência.
    • Na guia .NET, clique em Microsoft.Web.FtpServer.
    • Clique em OK.
  2. Adicione uma referência ao System.Web para o projeto:

    • Clique em Projeto e clique em Adicionar referência.
    • Na guia .NET, clique em System.Web.
    • Clique em OK.
  3. Adicione uma referência ao System.Configuration para o projeto:

    • Clique em Projeto e clique em Adicionar referência.
    • Na guia .NET, clique em System.Configuration.
    • Clique em OK.
  4. Adicione o código para a classe de autenticação:

    • No Gerenciador de Soluções, clique duas vezes no arquivo Class1.cs.

    • Remova o código existente.

    • Cole o código a seguir no editor:

      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. Salve e compile o projeto.

Observação

Se você não tiver usado as etapas opcionais para registrar os assemblies no GAC, será necessário copiar manualmente os assemblies para o computador do IIS e adicionar os assemblies ao GAC usando a ferramenta Gacutil.exe. Para obter mais informações, consulte Gacutil.exe (ferramenta de cache de assembly global).

Etapa 3: adicionar o provedor de demonstração ao FTP

Nesta etapa, você vai adicionar o provedor de demonstração ao serviço FTP e ao site padrão.

Adicionando o arquivo XML

Crie um arquivo XML para os usuários e funções associados:

  • Cole o seguinte código em um editor de texto:

    <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>
    
  • Salve o código como "Users.xml" em seu computador. Por exemplo, você pode usar o caminho C:\Inetpub\XmlSample\Users.xml.

Observação

Por motivos de segurança, esse arquivo não deve ser armazenado em uma pasta localizada na área de conteúdo do site.

Adicionando o provedor

  1. Determine as informações do assembly para o provedor de extensibilidade:

    • No Windows Explorer, abra o caminho C:\Windows\assembly, em que C: é a unidade do sistema operacional.
    • Localize o assembly FtpXmlAuthentication.
    • Clique com o botão direito do mouse no assembly e em Propriedades.
    • Copie o valor Cultura, por exemplo: Neutro.
    • Copie o número de Versão, por exemplo: 1.0.0.0.
    • Copie o valor do Token de Chave Pública, por exemplo: 426f62526f636b73.
    • Clique em Cancelar.
  2. Usando as informações das etapas anteriores, adicione o provedor de extensibilidade à lista global de provedores FTP e configure as opções para o provedor:

    • No momento, não há nenhuma interface de usuário que permita adicionar propriedades para um módulo de autenticação personalizada. Sendo assim, você terá que usar a seguinte linha de comando:

      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
      

    Observação

    O caminho do arquivo especificado no atributo xmlFileName deve corresponder ao caminho onde você salvou o arquivo "Users.xml" no computador anteriormente neste passo a passo.

  3. Adicione o provedor de autenticação personalizada para um site FTP:

    • Abra um site FTP no Gerenciador do IIS (Serviços de Informações da Internet).
    • Clique duas vezes na Autenticação FTP na janela principal.
    • Clique em Provedores personalizados... no painel Ações.
    • Verifique FtpXmlAuthentication na lista de provedores.
    • Clique em OK.
  4. Adicione uma regra de autorização para o provedor de autenticação:

    • Clique duas vezes em Regras de autorização FTP na janela principal.

    • Clique em Adicionar Regra de permissão... no painel Ações.

    • Você pode adicionar uma das seguintes regras de autorização:

      -Para um usuário específico:

      • Selecione Usuários especificados na opção de acesso.
      • Insira o nome de usuário. Por exemplo, usando a amostra de XML neste passo a passo, você pode inserir "Alice" ou "Bob".

      -Para uma função ou grupo:

      • Selecione Funções ou grupos de usuários especificados na opção de acesso.
      • Insira o nome da função ou grupo. Por exemplo, usando a amostra de XML neste passo a passo, você pode inserir "Membros" ou "Administradores".

      -Selecione Leitura e/ou Gravação na opção de Permissões.

    • Clique em OK.

Resumo

Neste passo a passo, você aprendeu a:

  • Criar um projeto no Visual Studio 2008 para um provedor de autenticação FTP personalizada.
  • Implementar a interface de extensibilidade para autenticação FTP personalizada.
  • Adicionar um provedor de autenticação personalizada ao seu serviço FTP.

Quando os usuários se conectam ao seu site FTP, o serviço FTP tentará autenticar os usuários com seu provedor de autenticação personalizada. Se isso falhar, o serviço FTP usará outros provedores internos ou de autenticação para autenticar usuários.