2009-04-07 38 views
53

Tôi hiện đang nghiên cứu các phương pháp lưu trữ vai trò người dùng và quyền đối với các dự án dựa trên .NET. Một số dự án này dựa trên web, một số dự án thì không. Tôi hiện đang đấu tranh để tìm phương pháp tốt nhất để đạt được những gì tôi đang tìm kiếm một cách nhất quán, di động trên các loại dự án.Quản lý vai trò và nhóm người dùng trong .NET với Active Directory

Tôi đang ở đâu, chúng tôi đang tìm cách tận dụng Active Directory làm điểm liên hệ duy nhất cho thông tin cơ bản của người dùng. Bởi vì điều này, chúng tôi đang tìm cách để không phải duy trì một cơ sở dữ liệu tùy chỉnh cho mỗi người dùng của ứng dụng vì chúng đã được lưu trữ trong Active Directory và chủ động duy trì ở đó. Ngoài ra, chúng tôi không muốn viết mô hình/mã bảo mật của chính mình nếu có thể và muốn sử dụng thứ gì đó có sẵn, như các khối ứng dụng bảo mật do Microsoft cung cấp.

Một số dự án chỉ yêu cầu các đặc quyền cơ bản, chẳng hạn như đọc, viết hoặc không có quyền truy cập. Các dự án khác yêu cầu quyền phức tạp hơn. Người dùng của các ứng dụng đó có thể được cấp quyền truy cập vào một số khu vực, nhưng không được cấp quyền truy cập cho các khu vực khác và quyền của họ có thể thay đổi trên từng khu vực. Một phần quản trị của ứng dụng sẽ kiểm soát và xác định quyền truy cập này, không phải là các công cụ AD.

Hiện tại, chúng tôi đang sử dụng Xác thực Windows được tích hợp để thực hiện xác thực trên mạng nội bộ của chúng tôi. Điều này làm việc tốt cho việc tìm kiếm thông tin người dùng cơ bản, và tôi đã thấy rằng ASP.NET có thể được mở rộng để cung cấp một nhà cung cấp vai trò Active Directory, vì vậy tôi có thể tìm ra bất kỳ nhóm bảo mật nào mà người dùng thuộc về. Nhưng, có vẻ như sự sụp đổ của phương pháp này đối với tôi là mọi thứ được lưu trữ trong Active Directory, điều này có thể dẫn đến một mớ hỗn độn để duy trì nếu mọi thứ phát triển quá lớn.

Cùng dòng này, tôi cũng đã nghe nói về Active Directory Lightweight Directory Services, có vẻ như nó có thể mở rộng lược đồ của chúng tôi và chỉ thêm các thuộc tính và nhóm ứng dụng cụ thể. Vấn đề là, tôi không thể tìm thấy bất cứ điều gì về cách này sẽ được thực hiện hoặc làm thế nào điều này hoạt động. Có những bài viết MSDN mô tả cách nói chuyện với cá thể này và cách tạo một cá thể mới, nhưng không có gì dường như trả lời câu hỏi của tôi.

Câu hỏi của tôi là: Dựa trên kinh nghiệm của bạn, tôi có đi đúng hướng không? Là những gì tôi đang tìm kiếm để làm có thể bằng cách sử dụng chỉ Active Directory, hoặc làm các công cụ khác phải được sử dụng?

phương pháp

khác Tôi đã nhìn vào:

  • Sử dụng nhiều file web.config [stackoverflow]
  • Tạo một mô hình bảo mật tùy chỉnh và cơ sở dữ liệu để quản lý người dùng trên các ứng dụng
+0

Tôi đã thêm mã mẫu, theo yêu cầu –

Trả lời

97

Sử dụng AD để xác thực của bạn là một ý tưởng tuyệt vời, vì bạn cần phải thêm mọi người ở đó, và cho nội bộ người dùng net không cần đăng nhập thêm. Bạn có chính xác rằng ASP.NET cho phép bạn sử dụng một Nhà cung cấp cho phép bạn xác thực với AD, mặc dù không có gì để hỗ trợ thành viên nhóm (mặc dù nó khá tầm thường để thực hiện nếu bạn muốn, tôi có thể cung cấp một mẫu).

Vấn đề thực sự ở đây là nếu bạn muốn sử dụng các nhóm AD để xác định quyền trong mỗi ứng dụng, phải không?

Nếu vậy thì bạn có tùy chọn tạo RoleProvider của riêng bạn cho ASP.NET cũng có thể được sử dụng bởi các ứng dụng WinForms và WPF thông qua ApplicationServices.RoleProvider này có thể liên kết ID của người dùng trong AD với các nhóm/vai trò trên mỗi ứng dụng mà bạn có thể lưu trữ trong cơ sở dữ liệu tùy chỉnh của riêng mình.

Nếu bạn muốn, bạn cũng có thể ghi đè và kết hợp vai trò ứng dụng với nhóm quảng cáo, vì vậy nếu họ ở trong nhóm "Quản trị" toàn cầu trong AD, họ được phép đầy đủ trong Ứng dụng bất kể tư cách thành viên vai trò ứng dụng. Ngược lại nếu họ có nhóm hoặc tài sản trong AD để nói rằng họ đã bị sa thải, bạn có thể bỏ qua tất cả tư cách thành viên vai trò ứng dụng và hạn chế tất cả quyền truy cập (vì nhân sự có thể sẽ không xóa họ khỏi mỗi ứng dụng, giả sử họ thậm chí biết về họ tất cả các!).

Mẫu mã bổ sung theo yêu cầu:

LƯU Ý: dựa trên tác phẩm này ban đầu http://www.codeproject.com/Articles/28546/Active-Directory-Roles-Provider

Đối ActiveDirectoryMembershipProvider của bạn, bạn chỉ cần thực hiện các phương pháp ValidateUser, mặc dù bạn có thể thực hiện hơn nếu bạn mong muốn, không gian tên AccountManagement mới làm cho điều này trở nên tầm thường:

// 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; 
} 

Vì nhà cung cấp vai trò của bạn có nhiều công việc hơn, có một số vấn đề chính mà chúng tôi đã phát hiện trong khi tìm kiếm trên google như nhóm bạn muốn loại trừ, người dùng bạn muốn loại trừ, v.v.

Đây có thể là một bài đăng đầy đủ trên blog, nhưng điều này sẽ giúp bạn bắt đầu. giống như một ví dụ về cách bạn có thể cải thiện hiệu suất (vì một mẫu Cache đầy đủ sẽ quá dài).

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); 
      } 
     } 
    } 
} 

Một mẫu cấu hình nhập tiểu mục này sẽ là như sau:

<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> 

Whew, đó là rất nhiều mã!

PS: Các phần cốt lõi của Nhà cung cấp vai trò ở trên dựa trên công việc của người khác, tôi không có liên kết thuận tiện nhưng chúng tôi đã tìm thấy nó qua Google, vì vậy một phần tín dụng cho người đó cho bản gốc. Chúng tôi sửa đổi nó rất nhiều để sử dụng LINQ và để thoát khỏi sự cần thiết cho một cơ sở dữ liệu cho bộ nhớ đệm.

+0

Điều đó nghe giống như những gì tôi đang tìm kiếm. Bạn có thể cung cấp bất kỳ mã mẫu nào không? Cảm ơn vì đã dành thời gian cho tôi! –

+7

Câu trả lời hay, bạn đã có sự tôn trọng của tôi – Ropstah

+0

Ví dụ này có yêu cầu thiết lập một ImpersonationContext không? Đang cố gắng làm một cái gì đó như thế này nhưng với xác thực Windows. – hometoast

Các vấn đề liên quan