使用Active Directory的.NET中的用户组和angular色pipe理

我目前正在研究存储基于.NET的项目的用户angular色和权限的方法。 其中一些项目是基于networking的,有些则不是。 我目前正在努力寻找最好的方法来实现我所期望的跨项目types的一致,可移植的方式。

我在哪里,我们正在考虑利用Active Directory作为我们的基本用户信息的单一联系点。 正因为如此,我们希望不必为每个应用程序的用户维护一个自定义数据库,因为它们已经存储在Active Directory中并在那里进行了主动维护。 另外,如果可能,我们不想编写自己的安全模型/代码,并且想要使用预先存在的东西,比如Microsoft提供的安全应用程序块。

有些项目只需要基本的权限,例如读取,写入或不访问。 其他项目需要更复杂的权限。 这些应用程序的用户可能被授予对某些区域的访问权限,但不允许其他用户访问这些应用程序,并且他们的权限可能会在每个区域发生变化 应用程序的pipe理部分将控制和定义此访问, 而不是 AD工具。

目前,我们正在使用集成的Windows身份validation在我们的Intranet上执行身份validation。 这很适合查找基本的用户信息,而且我已经看到ASP.NET可以扩展为提供Active Directoryangular色提供程序,所以我可以找出用户所属的任何安全组。 但是,对我来说,这种方法的垮台似乎是一切都存储在活动目录中,如果事情变得太大,可能会导致混乱。

沿着同样的路线,我也听说过Active Directory轻量级目录服务,它似乎可以扩展我们的模式并只添加特定于应用程序的属性和组。 问题是,我无法find任何这方面的工作或如何工作。 有MSDN的文章,描述如何与这个实例交谈,以及如何创build一个新的实例,但似乎没有任何回答我的问题。

我的问题是:根据你的经验,我正走在正确的轨道上吗? 是我正在寻找可能使用Active Directory,或其他工具必须使用?


我看过的其他方法:

  • 使用多个web.config文件[ stackoverflow ]
  • 创build自定义安全模型和数据库以跨应用程序pipe理用户

使用AD进行身份validation是一个好主意,因为无论如何您都需要添加所有人,对于Intranet用户,无需额外login。

你是正确的,ASP.NET允许你使用一个提供程序,它允许你对AD进行身份validation,尽pipe没有任何东西可以给你提供组成员支持(尽pipe如果你愿意的话,实现起来是相当简单的,我可以提供一个样本)。

这里真正的问题是,如果你想使用AD组来定义每个应用程序内的权限,是吗?

如果是这样,那么你可以select创build自己的ASP.NET的RoleProvider,也可以通过ApplicationServices由WinForms和WPF应用程序使用。 这个RoleProvider可以将AD用户的ID链接到每个应用程序的组/angular色,您可以将其存储在自己的自定义数据库中,这也允许每个应用程序允许pipe理这些angular色,而不需要这些pipe理员在AD中拥有额外的权限。

如果你愿意,你也可以拥有一个覆盖,并将应用程序angular色与AD组合在一起,所以如果他们在AD的某个全局“Admin”组中,他们将获得应用程序的完全权限,而不pipeAppangular色的成员身份。 相反,如果他们在AD中拥有一个组或者属性来表明他们已经被解雇了,那么你可以忽略所有的Appangular色成员资格,并且限制所有的访问权限(因为HR可能不会将他们从每个应用中移除,假设他们甚至知道他们所有!)。

根据请求添加示例代码:

注意:基于这个原始的工作http://www.codeproject.com/Articles/28546/Active-Directory-Roles-Provider

对于你的ActiveDirectoryMembershipProvider,你只需要实现ValidateUser方法,尽pipe你可以实现更多,如果你愿意,新的AccountManagement命名空间使这个微不足道的:

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

对于您的angular色提供商来说,这是一个更多的工作,我们在search谷歌时发现了一些关键问题,例如您想排除的组,您想排除的用户等。

这可能值得一个完整的博客文章,但这应该帮助你开始,它caching在会话variables的查找,就像你如何提高性能(因为一个完整的caching样本将太长)的样本。

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

一个样本configuration子部分条目如下:

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

呃,这是很多的代码!

PS:上面的angular色提供者的核心部分是基于另一个人的工作,我没有链接方便,但我们通过谷歌发现,所以原来的部分信贷给那个人。 我们对其进行了大量修改,以使用LINQ并摆脱数据库caching的需要。