2009-04-07 10 views
53

Attualmente sto ricercando metodi per archiviare ruoli utente e autorizzazioni per progetti basati su .NET. Alcuni di questi progetti sono basati sul web, altri no. Attualmente sto lottando per trovare il metodo migliore per ottenere ciò che sto cercando in un modo coerente e portatile tra i diversi tipi di progetti.Gestione utenti e ruoli in .NET con Active Directory

Dove mi trovo, stiamo cercando di sfruttare Active Directory come nostro unico punto di contatto per le informazioni utente di base. Per questo motivo, stiamo cercando di non dover mantenere un database personalizzato per gli utenti di ciascuna applicazione poiché sono già memorizzati in Active Directory e attivamente mantenuti lì. Inoltre, non vogliamo scrivere il nostro codice/modello di sicurezza, se possibile, e vorremmo usare qualcosa di preesistente, come i blocchi di applicazioni di sicurezza forniti da Microsoft.

Alcuni progetti richiedono solo privilegi di base, come lettura, scrittura o nessun accesso. Altri progetti richiedono autorizzazioni più complesse. Agli utenti di tali applicazioni potrebbe essere concesso l'accesso ad alcune aree, ma non ad altre, e le loro autorizzazioni possono cambiare in ciascuna area. Una sezione di amministrazione dell'app controllerebbe e definirà questo accesso, non gli strumenti di AD.

Attualmente stiamo utilizzando l'autenticazione di Windows integrata per eseguire l'autenticazione sulla nostra intranet. Questo funziona bene per scoprire le informazioni di base dell'utente e ho visto che ASP.NET può essere esteso per fornire un provider di ruoli di Active Directory, così posso scoprire tutti i gruppi di sicurezza a cui un utente appartiene. Ma, quello che mi sembra il crollo di questo metodo per me è che tutto è memorizzato in Active Directory, il che potrebbe portare a un disastro da mantenere se le cose diventeranno troppo grandi.

Lungo questa stessa linea, ho anche sentito parlare dei servizi di directory leggeri di Active Directory, che sembra possa estendere il nostro schema e aggiungere solo attributi e gruppi specifici dell'applicazione. Il problema è che non riesco a trovare nulla su come questo sarebbe stato fatto o come funziona. Esistono articoli MSDN che descrivono come parlare a questa istanza e come creare una nuova istanza, ma niente sembra mai rispondere alla mia domanda.

La mia domanda è: Sulla base della vostra esperienza, sto andando giù nella giusta direzione? È quello che sto cercando di fare usando solo Active Directory, o altri strumenti devono essere usati?


Altri metodi Ho esaminato:

  • Uso di più file web.config [stackoverflow]
  • Creazione di un modello di sicurezza personalizzato e database per gestire gli utenti attraverso applicazioni
+0

Ho aggiunto il codice di esempio, come richiesto –

risposta

97

L'utilizzo di AD per l'autenticazione è una grande idea, dal momento che è necessario aggiungere tutti comunque e per intra utenti della rete non è necessario un login extra.

Hai ragione che ASP.NET ti consente di utilizzare un Provider che ti permetterà di autenticarti contro AD, anche se non c'è niente incluso per darti il ​​supporto per l'appartenenza al gruppo (anche se è piuttosto semplice da implementare se vuoi, io può fornire un campione).

Il vero problema qui è se si desidera utilizzare i gruppi di annunci per definire le autorizzazioni all'interno di ciascuna app, sì?

Se è così, si ha la possibilità di creare il proprio RoleProvider per ASP.NET che può essere utilizzato anche da applicazioni WinForms e WPF tramite ApplicationServices.Questo RoleProvider può collegare l'ID dell'utente in AD a gruppi/ruoli per app che è possibile memorizzare nel proprio database personalizzato, il che consente a ciascuna app di consentire l'amministrazione di questi ruoli senza che questi amministratori abbiano privilegi aggiuntivi in ​​AD.

Se si vuole si può anche avere un override e combinare i ruoli app con gruppi di annunci, quindi se sono in qualche gruppo globale "Admin" in AD ottengono completa il permesso in App indipendentemente dall'appartenenza al ruolo App. Al contrario, se hanno un gruppo o una proprietà in AD per dire che sono stati licenziati, potresti ignorare l'appartenenza al ruolo dell'app e limitare tutti gli accessi (poiché probabilmente le risorse umane non li rimuovono da ogni singola app, assumendo che ne siano a conoscenza tutti!).

codice Campione aggiunto come richiesto:

NOTA: sulla base di questo lavoro originale http://www.codeproject.com/Articles/28546/Active-Directory-Roles-Provider

Per la vostra ActiveDirectoryMembershipProvider avete solo bisogno di implementare il metodo ValidateUser, anche se è possibile implementare più se si desidera, il nuovo spazio dei nomi AccountManagement rende questo banale:

// assumes: using System.DirectoryServices.AccountManagement; 
public override bool ValidateUser(string username, string password) 
{ 
    bool result = false; 

    try 
    { 
    using(var context = 
     new PrincipalContext(ContextType.Domain, "yourDomainName")) 
    { 
     result = context.ValidateCredentials(username, password); 
    } 
    } 
    catch(Exception ex) 
    { 
    // TODO: log exception 
    } 

    return result; 
} 

Per il vostro provider di ruoli è un po 'più di lavoro, non c'è alcuni problemi chiave che abbiamo scoperto durante la ricerca su google come i gruppi che si desidera escludere, gli utenti che si desidera escludere ecc.

Probabilmente merita un post completo sul blog, ma questo dovrebbe aiutare a iniziare, è la ricerca nella cache nelle variabili Session, proprio come un esempio di come potresti migliorare le prestazioni (dal momento che un campione completo di Cache sarebbe troppo lungo).

using System; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.Configuration.Provider; 
using System.Diagnostics; 
using System.DirectoryServices; 
using System.DirectoryServices.AccountManagement; 
using System.Linq; 
using System.Web; 
using System.Web.Hosting; 
using System.Web.Security; 

namespace MyApp.Security 
{ 
    public sealed class ActiveDirectoryRoleProvider : RoleProvider 
    { 
     private const string AD_FILTER = "(&(objectCategory=group)(|(groupType=-2147483646)(groupType=-2147483644)(groupType=-2147483640)))"; 
     private const string AD_FIELD = "samAccountName"; 

     private string _activeDirectoryConnectionString; 
     private string _domain; 

     // Retrieve Group Mode 
     // "Additive" indicates that only the groups specified in groupsToUse will be used 
     // "Subtractive" indicates that all Active Directory groups will be used except those specified in groupsToIgnore 
     // "Additive" is somewhat more secure, but requires more maintenance when groups change 
     private bool _isAdditiveGroupMode; 

     private List<string> _groupsToUse; 
     private List<string> _groupsToIgnore; 
     private List<string> _usersToIgnore; 

     #region Ignore Lists 

     // IMPORTANT - DEFAULT LIST OF ACTIVE DIRECTORY USERS TO "IGNORE" 
     //    DO NOT REMOVE ANY OF THESE UNLESS YOU FULLY UNDERSTAND THE SECURITY IMPLICATIONS 
     //    VERYIFY THAT ALL CRITICAL USERS ARE IGNORED DURING TESTING 
     private String[] _DefaultUsersToIgnore = new String[] 
     { 
      "Administrator", "TsInternetUser", "Guest", "krbtgt", "Replicate", "SERVICE", "SMSService" 
     }; 

     // IMPORTANT - DEFAULT LIST OF ACTIVE DIRECTORY DOMAIN GROUPS TO "IGNORE" 
     //    PREVENTS ENUMERATION OF CRITICAL DOMAIN GROUP MEMBERSHIP 
     //    DO NOT REMOVE ANY OF THESE UNLESS YOU FULLY UNDERSTAND THE SECURITY IMPLICATIONS 
     //    VERIFY THAT ALL CRITICAL GROUPS ARE IGNORED DURING TESTING BY CALLING GetAllRoles MANUALLY 
     private String[] _defaultGroupsToIgnore = new String[] 
      { 
       "Domain Guests", "Domain Computers", "Group Policy Creator Owners", "Guests", "Users", 
       "Domain Users", "Pre-Windows 2000 Compatible Access", "Exchange Domain Servers", "Schema Admins", 
       "Enterprise Admins", "Domain Admins", "Cert Publishers", "Backup Operators", "Account Operators", 
       "Server Operators", "Print Operators", "Replicator", "Domain Controllers", "WINS Users", 
       "DnsAdmins", "DnsUpdateProxy", "DHCP Users", "DHCP Administrators", "Exchange Services", 
       "Exchange Enterprise Servers", "Remote Desktop Users", "Network Configuration Operators", 
       "Incoming Forest Trust Builders", "Performance Monitor Users", "Performance Log Users", 
       "Windows Authorization Access Group", "Terminal Server License Servers", "Distributed COM Users", 
       "Administrators", "Everybody", "RAS and IAS Servers", "MTS Trusted Impersonators", 
       "MTS Impersonators", "Everyone", "LOCAL", "Authenticated Users" 
      }; 
     #endregion 

     /// <summary> 
     /// Initializes a new instance of the ADRoleProvider class. 
     /// </summary> 
     public ActiveDirectoryRoleProvider() 
     { 
      _groupsToUse = new List<string>(); 
      _groupsToIgnore = new List<string>(); 
      _usersToIgnore = new List<string>(); 
     } 

     public override String ApplicationName { get; set; } 

     /// <summary> 
     /// Initialize ADRoleProvider with config values 
     /// </summary> 
     /// <param name="name"></param> 
     /// <param name="config"></param> 
     public override void Initialize(String name, NameValueCollection config) 
     { 
      if (config == null) 
       throw new ArgumentNullException("config"); 

      if (String.IsNullOrEmpty(name)) 
       name = "ADRoleProvider"; 

      if (String.IsNullOrEmpty(config[ "description" ])) 
      { 
       config.Remove("description"); 
       config.Add("description", "Active Directory Role Provider"); 
      } 

      // Initialize the abstract base class. 
      base.Initialize(name, config); 

      _domain = ReadConfig(config, "domain"); 
      _isAdditiveGroupMode = (ReadConfig(config, "groupMode") == "Additive"); 
      _activeDirectoryConnectionString = ReadConfig(config, "connectionString"); 

      DetermineApplicationName(config); 
      PopulateLists(config); 
     } 

     private string ReadConfig(NameValueCollection config, string key) 
     { 
      if (config.AllKeys.Any(k => k == key)) 
       return config[ key ]; 

      throw new ProviderException("Configuration value required for key: " + key); 
     } 

     private void DetermineApplicationName(NameValueCollection config) 
     { 
      // Retrieve Application Name 
      ApplicationName = config[ "applicationName" ]; 
      if (String.IsNullOrEmpty(ApplicationName)) 
      { 
       try 
       { 
        string app = 
         HostingEnvironment.ApplicationVirtualPath ?? 
         Process.GetCurrentProcess().MainModule.ModuleName.Split('.').FirstOrDefault(); 

        ApplicationName = app != "" ? app : "/"; 
       } 
       catch 
       { 
        ApplicationName = "/"; 
       } 
      } 

      if (ApplicationName.Length > 256) 
       throw new ProviderException("The application name is too long."); 
     } 

     private void PopulateLists(NameValueCollection config) 
     { 
      // If Additive group mode, populate GroupsToUse with specified AD groups 
      if (_isAdditiveGroupMode && !String.IsNullOrEmpty(config[ "groupsToUse" ])) 
       _groupsToUse.AddRange(
        config[ "groupsToUse" ].Split(',').Select(group => group.Trim()) 
       ); 

      // Populate GroupsToIgnore List<string> with AD groups that should be ignored for roles purposes 
      _groupsToIgnore.AddRange(
       _defaultGroupsToIgnore.Select(group => group.Trim()) 
      ); 

      _groupsToIgnore.AddRange(
       (config[ "groupsToIgnore" ] ?? "").Split(',').Select(group => group.Trim()) 
      ); 

      // Populate UsersToIgnore ArrayList with AD users that should be ignored for roles purposes 
      string usersToIgnore = config[ "usersToIgnore" ] ?? ""; 
      _usersToIgnore.AddRange(
       _DefaultUsersToIgnore 
        .Select(value => value.Trim()) 
        .Union(
         usersToIgnore 
          .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) 
          .Select(value => value.Trim()) 
        ) 
      ); 
     } 

     private void RecurseGroup(PrincipalContext context, string group, List<string> groups) 
     { 
      var principal = GroupPrincipal.FindByIdentity(context, IdentityType.SamAccountName, group); 

      if (principal == null) 
       return; 

      List<string> res = 
       principal 
        .GetGroups() 
        .ToList() 
        .Select(grp => grp.Name) 
        .ToList(); 

      groups.AddRange(res.Except(groups)); 
      foreach (var item in res) 
       RecurseGroup(context, item, groups); 
     } 

     /// <summary> 
     /// Retrieve listing of all roles to which a specified user belongs. 
     /// </summary> 
     /// <param name="username"></param> 
     /// <returns>String array of roles</returns> 
     public override string[] GetRolesForUser(string username) 
     { 
      string sessionKey = "groupsForUser:" + username; 

      if (HttpContext.Current != null && 
       HttpContext.Current.Session != null && 
       HttpContext.Current.Session[ sessionKey ] != null 
      ) 
       return ((List<string>) (HttpContext.Current.Session[ sessionKey ])).ToArray(); 

      using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _domain)) 
      { 
       try 
       { 
        // add the users groups to the result 
        var groupList = 
         UserPrincipal 
          .FindByIdentity(context, IdentityType.SamAccountName, username) 
          .GetGroups() 
          .Select(group => group.Name) 
          .ToList(); 

        // add each groups sub groups into the groupList 
        foreach (var group in new List<string>(groupList)) 
         RecurseGroup(context, group, groupList); 

        groupList = groupList.Except(_groupsToIgnore).ToList(); 

        if (_isAdditiveGroupMode) 
         groupList = groupList.Join(_groupsToUse, r => r, g => g, (r, g) => r).ToList(); 

        if (HttpContext.Current != null) 
         HttpContext.Current.Session[ sessionKey ] = groupList; 

        return groupList.ToArray(); 
       } 
       catch (Exception ex) 
       { 
        // TODO: LogError("Unable to query Active Directory.", ex); 
        return new[] { "" }; 
       } 
      } 
     } 

     /// <summary> 
     /// Retrieve listing of all users in a specified role. 
     /// </summary> 
     /// <param name="rolename">String array of users</param> 
     /// <returns></returns> 
     public override string[] GetUsersInRole(String rolename) 
     { 
      if (!RoleExists(rolename)) 
       throw new ProviderException(String.Format("The role '{0}' was not found.", rolename)); 

      using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _domain)) 
      { 
       try 
       { 
        GroupPrincipal p = GroupPrincipal.FindByIdentity(context, IdentityType.SamAccountName, rolename); 

        return (

         from user in p.GetMembers(true) 
         where !_usersToIgnore.Contains(user.SamAccountName) 
         select user.SamAccountName 

        ).ToArray(); 
       } 
       catch (Exception ex) 
       { 
        // TODO: LogError("Unable to query Active Directory.", ex); 
        return new[] { "" }; 
       } 
      } 
     } 

     /// <summary> 
     /// Determine if a specified user is in a specified role. 
     /// </summary> 
     /// <param name="username"></param> 
     /// <param name="rolename"></param> 
     /// <returns>Boolean indicating membership</returns> 
     public override bool IsUserInRole(string username, string rolename) 
     { 
      return GetUsersInRole(rolename).Any(user => user == username); 
     } 

     /// <summary> 
     /// Retrieve listing of all roles. 
     /// </summary> 
     /// <returns>String array of roles</returns> 
     public override string[] GetAllRoles() 
     { 
      string[] roles = ADSearch(_activeDirectoryConnectionString, AD_FILTER, AD_FIELD); 

      return (

       from role in roles.Except(_groupsToIgnore) 
       where !_isAdditiveGroupMode || _groupsToUse.Contains(role) 
       select role 

      ).ToArray(); 
     } 

     /// <summary> 
     /// Determine if given role exists 
     /// </summary> 
     /// <param name="rolename">Role to check</param> 
     /// <returns>Boolean indicating existence of role</returns> 
     public override bool RoleExists(string rolename) 
     { 
      return GetAllRoles().Any(role => role == rolename); 
     } 

     /// <summary> 
     /// Return sorted list of usernames like usernameToMatch in rolename 
     /// </summary> 
     /// <param name="rolename">Role to check</param> 
     /// <param name="usernameToMatch">Partial username to check</param> 
     /// <returns></returns> 
     public override string[] FindUsersInRole(string rolename, string usernameToMatch) 
     { 
      if (!RoleExists(rolename)) 
       throw new ProviderException(String.Format("The role '{0}' was not found.", rolename)); 

      return (
       from user in GetUsersInRole(rolename) 
       where user.ToLower().Contains(usernameToMatch.ToLower()) 
       select user 

      ).ToArray(); 
     } 

     #region Non Supported Base Class Functions 

     /// <summary> 
     /// AddUsersToRoles not supported. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory. 
     /// </summary> 
     public override void AddUsersToRoles(string[] usernames, string[] rolenames) 
     { 
      throw new NotSupportedException("Unable to add users to roles. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory."); 
     } 

     /// <summary> 
     /// CreateRole not supported. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory. 
     /// </summary> 
     public override void CreateRole(string rolename) 
     { 
      throw new NotSupportedException("Unable to create new role. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory."); 
     } 

     /// <summary> 
     /// DeleteRole not supported. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory. 
     /// </summary> 
     public override bool DeleteRole(string rolename, bool throwOnPopulatedRole) 
     { 
      throw new NotSupportedException("Unable to delete role. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory."); 
     } 

     /// <summary> 
     /// RemoveUsersFromRoles not supported. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory. 
     /// </summary> 
     public override void RemoveUsersFromRoles(string[] usernames, string[] rolenames) 
     { 
      throw new NotSupportedException("Unable to remove users from roles. For security and management purposes, ADRoleProvider only supports read operations against Active Direcory."); 
     } 
     #endregion 

     /// <summary> 
     /// Performs an extremely constrained query against Active Directory. Requests only a single value from 
     /// AD based upon the filtering parameter to minimize performance hit from large queries. 
     /// </summary> 
     /// <param name="ConnectionString">Active Directory Connection String</param> 
     /// <param name="filter">LDAP format search filter</param> 
     /// <param name="field">AD field to return</param> 
     /// <returns>String array containing values specified by 'field' parameter</returns> 
     private String[] ADSearch(String ConnectionString, String filter, String field) 
     { 
      DirectorySearcher searcher = new DirectorySearcher 
      { 
       SearchRoot = new DirectoryEntry(ConnectionString), 
       Filter = filter, 
       PageSize = 500 
      }; 
      searcher.PropertiesToLoad.Clear(); 
      searcher.PropertiesToLoad.Add(field); 

      try 
      { 
       using (SearchResultCollection results = searcher.FindAll()) 
       { 
        List<string> r = new List<string>(); 
        foreach (SearchResult searchResult in results) 
        { 
         var prop = searchResult.Properties[ field ]; 
         for (int index = 0; index < prop.Count; index++) 
          r.Add(prop[ index ].ToString()); 
        } 

        return r.Count > 0 ? r.ToArray() : new string[ 0 ]; 
       } 
      } 
      catch (Exception ex) 
      { 
       throw new ProviderException("Unable to query Active Directory.", ex); 
      } 
     } 
    } 
} 

Una voce sottosezione campione config per questo potrebbe essere il seguente:

<roleManager enabled="true" defaultProvider="ActiveDirectory"> 
    <providers> 
    <clear/> 
    <add 
     applicationName="MyApp" name="ActiveDirectory" 
     type="MyApp.Security.ActiveDirectoryRoleProvider" 
     domain="mydomain" groupMode="" connectionString="LDAP://myDirectoryServer.local/dc=mydomain,dc=local" 
    /> 
    </providers> 
</roleManager> 

Accidenti, che è un sacco di codice!

PS: Le parti principali del fornitore di ruolo sopra sono basate sul lavoro di un'altra persona, non ho il collegamento a portata di mano ma l'abbiamo trovato tramite Google, quindi credito parziale a quella persona per l'originale. Lo abbiamo modificato pesantemente per utilizzare LINQ e per eliminare la necessità di un database per la memorizzazione nella cache.

+0

Sembra proprio quello che stavo cercando. Sareste in grado di fornire qualsiasi codice di esempio? Grazie per il tuo tempo! –

+7

Ottima risposta, hai il mio rispetto – Ropstah

+0

Questo esempio richiede l'impostazione di un ImpersonationContext? Cercando di fare qualcosa di simile ma con l'autenticazione di Windows. – hometoast

Problemi correlati