使用Active Directory在.NET中管理用户组和角色
我目前正在研究存储基于.NET项目的用户角色和权限的方法。这些项目中有些是基于网络的,有些不是。我目前正在努力寻找最佳的方法,以便在不同的项目类型中以一致、可移植的方式实现我所寻找的目标 就我而言,我们希望利用Active Directory作为我们获取基本用户信息的单一联系人。正因为如此,我们希望不必为每个应用程序的用户维护自定义数据库,因为它们已经存储在Active Directory中并在那里进行了主动维护。此外,如果可能的话,我们不想编写自己的安全模型/代码,而是希望使用一些预先存在的东西,如Microsoft提供的安全应用程序块 有些项目只需要基本权限,如读、写或无访问权限。其他项目需要更复杂的权限。这些应用程序的用户可能被授予访问某些区域的权限,但不能访问其他区域,并且他们的权限可以在每个区域中更改。应用程序的管理部分将控制和定义此访问,而不是广告工具 目前,我们正在使用集成的Windows身份验证在我们的intranet上执行身份验证。这对于查找基本用户信息非常有效,而且我已经看到ASP.NET可以扩展为提供Active Directory角色提供程序,因此我可以查找用户所属的任何安全组。但是,在我看来,这种方法的失败之处在于,所有东西都存储在Active Directory中,如果事情变得太大,可能会导致维护混乱 同样,我也听说过Active Directory轻型目录服务,它似乎可以扩展我们的模式,只添加特定于应用程序的属性和组。问题是,我找不到任何关于如何做到这一点或这是如何工作的。有一些MSDN文章描述了如何与这个实例对话以及如何创建一个新实例,但似乎没有任何东西能够回答我的问题 我的问题是:根据你的经验,我走的是正确的道路吗?我想做的仅仅是使用Active Directory是可能的,还是必须使用其他工具使用Active Directory在.NET中管理用户组和角色,.net,asp.net,security,active-directory,.net,Asp.net,Security,Active Directory,我目前正在研究存储基于.NET项目的用户角色和权限的方法。这些项目中有些是基于网络的,有些不是。我目前正在努力寻找最佳的方法,以便在不同的项目类型中以一致、可移植的方式实现我所寻找的目标 就我而言,我们希望利用Active Directory作为我们获取基本用户信息的单一联系人。正因为如此,我们希望不必为每个应用程序的用户维护自定义数据库,因为它们已经存储在Active Directory中并在那里进行了主动维护。此外,如果可能的话,我们不想编写自己的安全模型/代码,而是希望使用一些预先存在的东
我研究过的其他方法:
- 使用多个web.config文件[]
- 创建自定义安全模型和数据库以跨应用程序管理用户
// 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;
}
对于你的角色提供者来说,这需要做更多的工作,我们在搜索google时发现了一些关键问题,比如你想要排除的组,你想要排除的用户等等
这可能值得一篇完整的博客文章,但这应该可以帮助您开始,它是在会话变量中缓存查找,只是作为如何提高性能的示例(因为完整缓存示例太长)
使用系统;
使用System.Collections.Generic;
使用System.Collections.Specialized;
使用System.Configuration.Provider;
使用系统诊断;
使用System.DirectoryServices;
使用System.DirectoryServices.AccountManagement;
使用System.Linq;
使用System.Web;
使用System.Web.Hosting;
使用System.Web.Security;
名称空间MyApp.Security
{
公共密封类ActiveDirectoryRoleProvider:RoleProvider
{
private const string AD_FILTER=“(&(objectCategory=group)(|(groupType=-2147483646)(groupType=-2147483644)(groupType=-2147483640))”;
private const string AD_FIELD=“samAccountName”;
私有字符串\u activeDirectoryConnectionString;
私有字符串_域;
//检索组模式
//“加法”表示仅使用groupsToUse中指定的组
//“减法”表示将使用除groupsToIgnore中指定的组之外的所有Active Directory组
//“添加剂”在某种程度上更安全,但在组更改时需要更多维护
私有布尔是可加性分组模式;
私人名单(分组使用);;
私有列表_groupsToIgnore;
私有列表_usersToIgnore;
#区域忽略列表
//重要信息-要“忽略”的ACTIVE DIRECTORY用户的默认列表
//别再胡闹了
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 );
}
}
}
}
<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>