活动目录访问和 IIS 身份验证方法问题
Active directory access and IIS Authetication method issue
我有一个 Intranet 应用程序可以根据 Active Directory 对用户进行身份验证。一般来说,它在本地测试时工作正常(即从我的开发机器使用 VS 2017)或 运行 在应用程序服务器("Browse *:80")中关闭 IIS,直到我尝试使用其 [=44= 访问它] 从我的本地机器。然后,无论我使用什么用户 ID 来获取用户的详细信息,都不会显示任何内容。
此外,某些组中的用户可以访问此应用程序,因此应用程序检查登录用户的组成员身份。
以下是我在 IIS 中的设置方式以及我测试的不同场景:
- 我将身份验证设置为 "Windows Authentication",禁用匿名
身份验证并启用 AS.NET 模拟。这工作正常时
运行 从应用服务器使用本地主机但在尝试访问时
从我的本地计算机并提供用户的用户 ID 以获取 his/her
详细信息,不显示任何详细信息(无法从中获取任何信息
广告)。
- 如果我启用匿名身份验证并将其设置为 "Application Pool Identity"(即网络服务),它会显示我的自定义
"Access Denied" 页面,大概是因为该用户不属于
允许的组。
- 如果我启用匿名身份验证并 select "Specific User",提供我的凭据,那么一切正常,从应用程序服务器或从我的本地机器,有一个警告:无论谁在访问网站,它显示我的名字作为登录用户。
我很困惑,希望得到一些提示。
更新 - 添加了获取用户信息的代码
获取用户身份的代码:
WindowsIdentity wiUser = WindowsIdentity.GetCurrent();
string sID = wiUser.Name.ToUpper();
获取用户广告信息的代码:
static string adDomain = "www.xxx.yyy.zzz";
static string adContainer = "DC=www,DC=xxx,DC=yyy,DC=zzz";
public static DataTable getUserADInfoDT(string sSearchStr)
{
DataTable dtResults = new DataTable();
dtResults.Columns.Add("ID");
dtResults.Columns.Add("FirstName");
...
dtResults.Columns.Add("Zip");
string adDomain = string.Empty;
string adContainer = string.Empty;
// create domain context
PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, adDomain, adContainer);
using (adPrincipalContext)
{
// define a "query-by-example" principal
UserPrincipal qbeUser = new UserPrincipal(adPrincipalContext);
qbeUser.SamAccountName = sSearchStr.Trim().ToUpper();
// create principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> psr = srch.FindAll();
// find all matches
foreach (var found in psr)
{
DataRow dr = dtResults.NewRow();
UserPrincipal up = (UserPrincipal)found;
DirectoryEntry de = (DirectoryEntry)up.GetUnderlyingObject();
dr["ID"] = de.Properties["SAMAccountName"].Value.ToString().ToUpper();
if (de.Properties["givenName"].Value != null)
dr["FirstName"] = de.Properties["givenName"].Value.ToString();
...
if (de.Properties["postalCode"].Value != null)
dr["Zip"] = de.Properties["postalCode"].Value.ToString();
dtResults.Rows.Add(dr);
//de.Close();
}
return dtResults;
}
}
WindowsIdentity.GetCurrent()
会得到当前进程在运行下的用户账号。如果应用程序在 IIS 中是 运行,那通常是应用程序池所在的用户帐户 运行 - 而不是登录到您的应用程序的用户。
如果您在本地使用 IIS Express 进行调试,那么 IIS Express 在您的用户帐户下 运行,因此您看不出有什么不同。但是在服务器上你会。
就是说,如果您启用了模拟并正常工作,那么 WindowsIdentity.GetCurrent()
应该 return 登录的用户 - 因为模拟意味着您的应用现在假装是那个人。所以这可能意味着模拟设置不正确。但也许你甚至不需要它。我个人从未发现需要使用模拟。
要让用户登录到您的应用程序,您应该使用 HttpRequest.Current.User
或者如果您的应用程序是 ASP.NET MVC,则可能只使用 HttpContext.User
。
实际上有一种更简单的方法可以为用户获取 DirectoryEntry
对象,而无需搜索用户名。可以直接绑定用户的SID,这是你已有的信息:
var user = new DirectoryEntry(
$"LDAP://<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
如果您的服务器未加入与您的用户相同的域,那么您可能需要包含域名:
var user = new DirectoryEntry(
$"LDAP://www.xxx.yyy.zzz/<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
还有一点要记住:DirectoryEntry
保留所有 Properties
的缓存。当您访问缓存中没有的 属性 时,它会转到 AD 并请求 每个具有值 的属性。如果您只需要 3 个属性,那将是大量无用的数据通过网络传输。所以你可以告诉它在你使用它们之前只特别要求这 4 个属性:
user.RefreshCache(new[] {"SAMAccountName", "givenName", "postalCode"});
我在一篇文章中谈到了这一点和其他要点:Active Directory: Better Performance
更新: 要验证 IIS 是否确实对页面强制执行身份验证,您可以在 PowerShell 中执行此操作(将 URL 更新为您需要的 - 这至少需要 PowerShell 3):
try { (Invoke-WebRequest "http://example.com/the/page").StatusCode }
catch { $_.Exception.Response.StatusCode.Value__}
那会输出回复的状态码。您 应该 看到 401
,因为那是 IIS 挑战浏览器发送凭据的方式。如果您看到 200
,则 Windows 身份验证不适用于该页面。
我在 PowerShell 中执行此操作,因为 Chrome 的开发工具甚至不会向您显示 401 挑战。
我有一个 Intranet 应用程序可以根据 Active Directory 对用户进行身份验证。一般来说,它在本地测试时工作正常(即从我的开发机器使用 VS 2017)或 运行 在应用程序服务器("Browse *:80")中关闭 IIS,直到我尝试使用其 [=44= 访问它] 从我的本地机器。然后,无论我使用什么用户 ID 来获取用户的详细信息,都不会显示任何内容。
此外,某些组中的用户可以访问此应用程序,因此应用程序检查登录用户的组成员身份。
以下是我在 IIS 中的设置方式以及我测试的不同场景:
- 我将身份验证设置为 "Windows Authentication",禁用匿名 身份验证并启用 AS.NET 模拟。这工作正常时 运行 从应用服务器使用本地主机但在尝试访问时 从我的本地计算机并提供用户的用户 ID 以获取 his/her 详细信息,不显示任何详细信息(无法从中获取任何信息 广告)。
- 如果我启用匿名身份验证并将其设置为 "Application Pool Identity"(即网络服务),它会显示我的自定义 "Access Denied" 页面,大概是因为该用户不属于 允许的组。
- 如果我启用匿名身份验证并 select "Specific User",提供我的凭据,那么一切正常,从应用程序服务器或从我的本地机器,有一个警告:无论谁在访问网站,它显示我的名字作为登录用户。
我很困惑,希望得到一些提示。
更新 - 添加了获取用户信息的代码
获取用户身份的代码:
WindowsIdentity wiUser = WindowsIdentity.GetCurrent();
string sID = wiUser.Name.ToUpper();
获取用户广告信息的代码:
static string adDomain = "www.xxx.yyy.zzz";
static string adContainer = "DC=www,DC=xxx,DC=yyy,DC=zzz";
public static DataTable getUserADInfoDT(string sSearchStr)
{
DataTable dtResults = new DataTable();
dtResults.Columns.Add("ID");
dtResults.Columns.Add("FirstName");
...
dtResults.Columns.Add("Zip");
string adDomain = string.Empty;
string adContainer = string.Empty;
// create domain context
PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, adDomain, adContainer);
using (adPrincipalContext)
{
// define a "query-by-example" principal
UserPrincipal qbeUser = new UserPrincipal(adPrincipalContext);
qbeUser.SamAccountName = sSearchStr.Trim().ToUpper();
// create principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> psr = srch.FindAll();
// find all matches
foreach (var found in psr)
{
DataRow dr = dtResults.NewRow();
UserPrincipal up = (UserPrincipal)found;
DirectoryEntry de = (DirectoryEntry)up.GetUnderlyingObject();
dr["ID"] = de.Properties["SAMAccountName"].Value.ToString().ToUpper();
if (de.Properties["givenName"].Value != null)
dr["FirstName"] = de.Properties["givenName"].Value.ToString();
...
if (de.Properties["postalCode"].Value != null)
dr["Zip"] = de.Properties["postalCode"].Value.ToString();
dtResults.Rows.Add(dr);
//de.Close();
}
return dtResults;
}
}
WindowsIdentity.GetCurrent()
会得到当前进程在运行下的用户账号。如果应用程序在 IIS 中是 运行,那通常是应用程序池所在的用户帐户 运行 - 而不是登录到您的应用程序的用户。
如果您在本地使用 IIS Express 进行调试,那么 IIS Express 在您的用户帐户下 运行,因此您看不出有什么不同。但是在服务器上你会。
就是说,如果您启用了模拟并正常工作,那么 WindowsIdentity.GetCurrent()
应该 return 登录的用户 - 因为模拟意味着您的应用现在假装是那个人。所以这可能意味着模拟设置不正确。但也许你甚至不需要它。我个人从未发现需要使用模拟。
要让用户登录到您的应用程序,您应该使用 HttpRequest.Current.User
或者如果您的应用程序是 ASP.NET MVC,则可能只使用 HttpContext.User
。
实际上有一种更简单的方法可以为用户获取 DirectoryEntry
对象,而无需搜索用户名。可以直接绑定用户的SID,这是你已有的信息:
var user = new DirectoryEntry(
$"LDAP://<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
如果您的服务器未加入与您的用户相同的域,那么您可能需要包含域名:
var user = new DirectoryEntry(
$"LDAP://www.xxx.yyy.zzz/<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
还有一点要记住:DirectoryEntry
保留所有 Properties
的缓存。当您访问缓存中没有的 属性 时,它会转到 AD 并请求 每个具有值 的属性。如果您只需要 3 个属性,那将是大量无用的数据通过网络传输。所以你可以告诉它在你使用它们之前只特别要求这 4 个属性:
user.RefreshCache(new[] {"SAMAccountName", "givenName", "postalCode"});
我在一篇文章中谈到了这一点和其他要点:Active Directory: Better Performance
更新: 要验证 IIS 是否确实对页面强制执行身份验证,您可以在 PowerShell 中执行此操作(将 URL 更新为您需要的 - 这至少需要 PowerShell 3):
try { (Invoke-WebRequest "http://example.com/the/page").StatusCode }
catch { $_.Exception.Response.StatusCode.Value__}
那会输出回复的状态码。您 应该 看到 401
,因为那是 IIS 挑战浏览器发送凭据的方式。如果您看到 200
,则 Windows 身份验证不适用于该页面。
我在 PowerShell 中执行此操作,因为 Chrome 的开发工具甚至不会向您显示 401 挑战。