用户组和角色管理的.NET与Active Directory [英] User Group and Role Management in .NET with Active Directory

查看:254
本文介绍了用户组和角色管理的.NET与Active Directory的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在研究用于存储用户的角色和权限基于.NET的项目的方法。其中的一些项目都是基于网络的,有些则不是。目前,我正在努力寻找最好的方式来实现我正在寻找在整个项目类型一致的,可移植的方式。

I'm currently researching methods for storing user roles and permissions for .NET based projects. Some of these projects are web based, some are not. I'm currently struggling to find the best method to achieve what I'm looking for in a consistent, portable way across project types.

我在哪里,我们希望利用Active Directory作为我们的单一联系点,基本用户信息。正因为如此,我们正在寻找不必为每一个应用程序的用户自定义数据库,因为它们已经被存储在Active Directory和积极维护那里。此外,我们不希望如果可能的话写我们自己的安全模型/ code和想用的东西pre-现有的,像微软提供的安全应用程序块。

Where I'm at, we're looking to leverage Active Directory as our single point of contact for basic user information. Because of this, we're looking to not have to maintain a custom database for each application's users since they are already stored in Active Directory and actively maintained there. Additionally, we don't want to write our own security model/code if possible and would like to use something pre-existing, like the security application blocks provided by Microsoft.

有些项目只需要基本的权限,比如读取,写入,或不能访问。其他项目需要更复杂的权限。这些应用程序的用户可能会被允许访问某些领域,而不是其他人,他们的权限,可以在每个区域发生改变。该应用程序的管理部分将控制并定义此访问,不可以的广告工具。

Some projects require only basic privileges, such as read, write, or no access. Other projects require more complex permissions. Users of those applications might be granted access to some areas, but not others, and their permissions can change across each area. An administration section of the app would control and define this access, not the AD tools.

目前,我们使用的是集成的Windows身份验证对我们的内联网进行认证。这非常适用于找出基本的用户信息,我已经看到了ASP.NET可以扩展到提供一个Active Directory角色提供者,这样我就可以找出用户属于任何安全组。但是,什么好像这个方法对我的垮台是一切都存储在Active Directory,这可能导致一个烂摊子保持如果事情增长过大。

Currently, we're using integrated Windows Authentication to perform authentication on our intranet. This works well for finding out basic user information, and I've seen that ASP.NET can be extended to provide an Active Directory roles provider, so I can find out any security groups a user belongs to. But, what seems like the downfall of this method to me is that everything is stored in Active Directory, which could lead to a mess to maintain if things grow too big.

沿着这同一行,我也听说Active Directory轻型目录服务,这似乎是它可以扩展我们的架构和只添加应用程序特定的属性和组。问题是,我无法找到如何做到这一点做的还是如何工作的事情。有迹象表明,描述如何与此实例以及如何创建一个新的实例MSDN文章,但从来都没有,似乎回答我的问题。

Along this same line, I've also heard of Active Directory Lightweight Directory Services, which seems like it could extend our schema and add only application specific attributes and groups. Problem is, I can't find anything on how this would be done or how this works. There are MSDN articles that describe how to talk to this instance and how to create a new instance, but nothing ever seems to answer my question.

我的问题是:根据你的经验,我该怎么下来了正确的轨道?就是我找只使用活动目录做可能,或做其他工具必须使用?

My question is: Based on your experience, am I going down the right track? Is what I'm looking to do possible using just Active Directory, or do other tools have to be used?


其他方法我已经研究过:

  • 在使用多个web.config文件中[<一href="http://stackoverflow.com/questions/176338/customer-configurable-asp-net-web-site-security-for-fine-grained-control-of-page">stackoverflow]
  • 在创建自定义的安全模型和数据库,跨应用程序管理用户

推荐答案

使用AD进行身份验证是个好主意,因为你需要添加每个人有反正,并为内网用户有没有需要额外的登录。

Using AD for your authentication is a great idea, since you need to add everyone there anyway, and for intranet users there's no need for an extra login.

你是正确的ASP.NET允许您使用一个供应商,这将使你来验证AD,虽然没有什么包括给你组成员的支持(虽然它很容易实现,如果你想要,我可以提供样本)。

You're correct that ASP.NET allows you to use a Provider which will allow you to authenticate against AD, although there's nothing included to give you group membership support (although it's quite trivial to implement if you want to, I can provide a sample).

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

The real issue here is if you want to use AD groups to define permissions within each app, yes?

如果是的话,你有创建自己的RoleProvider为ASP.NET的,也可以使用通过ApplicationServices WinForms和WPF应用程序的选项。这RoleProvider可以在广告链接的用户的ID,以每个应用程序组/角色,你可以在自己的自定义数据库,这也让每一个应用程序,让这些角色的管理,而不需要这些管理员有额外的特权在公元存储。

If so then you do have the option of creating your own RoleProvider for ASP.NET that can also be used by WinForms and WPF apps via ApplicationServices. This RoleProvider could link the ID of the user in AD to groups/roles per app which you can store in your own custom database, which also allows each app to allow administration of these roles without requiring these admins to have extra privileges in AD.

。反之,如果他们有一个组或者财产公元说,他们已经被解雇,你可以忽略所有应用程序的角色成员资格,并限制所有访问(因为人力资源可能不会从每一个应用程序删除它们,假设他们甚至知道他们所有!)。

If you want you can also have an override and combine app roles with AD groups, so if they're in some global "Admin" group in AD they get full permission in the App regardless of App role membership. Conversely if they have either a group or property in AD to say they've been fired you could ignore all App role membership and restrict all access (since HR probably wouldn't remove them from each and every app, assuming they even know about them all!).

样品code添加的要求:

Sample code added as requested:

注意:在此基础上的原创作品<一href="http://www.$c$cproject.com/Articles/28546/Active-Directory-Roles-Provider">http://www.$c$cproject.com/Articles/28546/Active-Directory-Roles-Provider

有关您的ActiveDirectoryMembershipProvider你只需要实现ValidateUser方法,虽然可以实现,如果你需要的更多的,新的AccountManagement命名空间使这琐碎的:

For your ActiveDirectoryMembershipProvider you only need to implement the ValidateUser method, although you could implement more if you desired, the new AccountManagement namespace makes this trivial:

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

有关您的角色提供它是一个多一点的工作,还有我们发现,虽然谷歌搜索的一些关键问题,如要排除组,要排除等用户。

For your role provider it's a little bit more work, there's some key issues we discovered while searching google such as groups you want to exclude, users you want to exclude etc.

这可能是值得一个完整的博客文章,但这应该帮助您开始,它的缓存查找在Session变量,就如同你如何能提高性能(因为完整的缓存样本就太长了)的样本。

It's probably worth a full blog post, but this should help you get started, it's caching lookups in Session variables, just as a sample of how you could improve performance (since a full Cache sample would be too long).

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

有关这方面的一个示例配置子部分条目将是如下:

A sample config sub-section entry for this would be as follows:

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

呼,这是一个很大的code!

Whew, that's a lot of code!

PS:角色提供的核心部件上面的是基于另一个人的工作,我没有方便的链接,但我们通过谷歌,所以部分信贷发现该人原。我们修改了它大量使用LINQ和摆脱需要缓存的数据库。

PS: Core parts of the Role Provider above are based on another person's work, I don't have the link handy but we found it via Google, so partial credit to that person for the original. We modified it heavily to use LINQ and to get rid of the need for a database for caching.

这篇关于用户组和角色管理的.NET与Active Directory的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆