如何使用 Windows Active Directory 身份验证和基于身份的声明?
How to use Windows Active Directory Authentication and Identity Based Claims?
问题
我们想使用 Windows Active Directory 对应用程序中的用户进行身份验证。但是,我们不想使用 Active Directory 组来管理 controllers/views.
的授权
据我所知,没有一种简单的方法可以将 AD 和基于身份的声明结合起来。
目标
- 使用本地 Active Directory 验证用户
- 使用身份框架管理声明
尝试(失败)
- Windows.Owin.Security.ActiveDirectory - 哦。这是针对 Azure AD 的。不支持 LDAP。他们可以将其称为 AzureActiveDirectory 吗?
- Windows 身份验证 - 这对 NTLM 或 Keberos 身份验证没问题。问题始于:i) 令牌和声明都由 AD 管理,我无法弄清楚如何使用它来使用身份声明。
- LDAP - 但是这些似乎迫使我手动进行表单身份验证才能使用身份声明?当然必须有更简单的方法吗?
如有任何帮助,我们将不胜感激。我已经在这个问题上停留了很长时间,并希望得到外界对此事的意见。
您知道如何实现自定义 System.Web.Security.MembershipProvider
吗?您应该能够将此(覆盖 ValidateUser
)与 System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials()
结合使用以针对活动目录进行身份验证。
尝试:
var pc = new PrincipalContext(ContextType.Domain, "example.com", "DC=example,DC=com");
pc.ValidateCredentials(username, password);
只需使用用户名和密码访问 AD,而不是针对您的数据库进行身份验证
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.UserName);
if (user != null && AuthenticateAD(model.UserName, model.Password))
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
public bool AuthenticateAD(string username, string password)
{
using(var context = new PrincipalContext(ContextType.Domain, "MYDOMAIN"))
{
return context.ValidateCredentials(username, password);
}
}
采用你上面的解决方案将我推向了在 MVC6-Beta3 Identityframework7-Beta3 EntityFramework7-Beta3 上对我有用的方向:
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
//
// Check for user existance in Identity Framework
//
ApplicationUser applicationUser = await _userManager.FindByNameAsync(model.eID);
if (applicationUser == null)
{
ModelState.AddModelError("", "Invalid username");
return View(model);
}
//
// Authenticate user credentials against Active Directory
//
bool isAuthenticated = await Authentication.ValidateCredentialsAsync(
_applicationSettings.Options.DomainController,
_applicationSettings.Options.DomainControllerSslPort,
model.eID, model.Password);
if (isAuthenticated == false)
{
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
//
// Signing the user step 1.
//
IdentityResult identityResult
= await _userManager.CreateAsync(
applicationUser,
cancellationToken: Context.RequestAborted);
if(identityResult != IdentityResult.Success)
{
foreach (IdentityError error in identityResult.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View(model);
}
//
// Signing the user step 2.
//
await _signInManager.SignInAsync(applicationUser,
isPersistent: false,
authenticationMethod:null,
cancellationToken: Context.RequestAborted);
return RedirectToLocal(returnUrl);
}
在 ASPNET5 (beta6) 上,想法是使用 CookieAuthentication 和 Identity:您需要在您的 Startup class 中添加:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization();
services.AddIdentity<MyUser, MyRole>()
.AddUserStore<MyUserStore<MyUser>>()
.AddRoleStore<MyRoleStore<MyRole>>()
.AddUserManager<MyUserManager>()
.AddDefaultTokenProviders();
}
在配置部分,添加:
private void ConfigureAuth(IApplicationBuilder app)
{
// Use Microsoft.AspNet.Identity & Cookie authentication
app.UseIdentity();
app.UseCookieAuthentication(options =>
{
options.AutomaticAuthentication = true;
options.LoginPath = new PathString("/App/Login");
});
}
然后,您需要执行:
Microsoft.AspNet.Identity.IUserStore
Microsoft.AspNet.Identity.IRoleStore
Microsoft.AspNet.Identity.IUserClaimsPrincipalFactory
和extend/override:
Microsoft.AspNet.Identity.UserManager
Microsoft.AspNet.Identity.SignInManager
我实际上已经设置了一个示例项目来展示如何做到这一点。
GitHub Link.
我在 beta8 上进行了测试,并且使用了一些小的适配(比如 Context => HttpContext)它也能正常工作。
您可以使用 ClaimTransformation,我今天下午刚刚使用下面的文章和代码让它工作。我正在使用 Window 身份验证访问应用程序,然后根据存储在 SQL 数据库中的权限添加声明。这是一篇很好的文章,应该对你有所帮助。
https://github.com/aspnet/Security/issues/863
综上所述...
services.AddScoped<IClaimsTransformer, ClaimsTransformer>();
app.UseClaimsTransformation(async (context) =>
{
IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return await transformer.TransformAsync(context);
});
public class ClaimsTransformer : IClaimsTransformer
{
private readonly DbContext _context;
public ClaimsTransformer(DbContext dbContext)
{
_context = dbContext;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
System.Security.Principal.WindowsIdentity windowsIdentity = null;
foreach (var i in context.Principal.Identities)
{
//windows token
if (i.GetType() == typeof(System.Security.Principal.WindowsIdentity))
{
windowsIdentity = (System.Security.Principal.WindowsIdentity)i;
}
}
if (windowsIdentity != null)
{
//find user in database by username
var username = windowsIdentity.Name.Remove(0, 6);
var appUser = _context.User.FirstOrDefault(m => m.Username == username);
if (appUser != null)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim("Id", Convert.ToString(appUser.Id)));
/*//add all claims from security profile
foreach (var p in appUser.Id)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(p.Permission, "true"));
}*/
}
}
return await System.Threading.Tasks.Task.FromResult(context.Principal);
}
}
问题
我们想使用 Windows Active Directory 对应用程序中的用户进行身份验证。但是,我们不想使用 Active Directory 组来管理 controllers/views.
的授权据我所知,没有一种简单的方法可以将 AD 和基于身份的声明结合起来。
目标
- 使用本地 Active Directory 验证用户
- 使用身份框架管理声明
尝试(失败)
- Windows.Owin.Security.ActiveDirectory - 哦。这是针对 Azure AD 的。不支持 LDAP。他们可以将其称为 AzureActiveDirectory 吗?
- Windows 身份验证 - 这对 NTLM 或 Keberos 身份验证没问题。问题始于:i) 令牌和声明都由 AD 管理,我无法弄清楚如何使用它来使用身份声明。
- LDAP - 但是这些似乎迫使我手动进行表单身份验证才能使用身份声明?当然必须有更简单的方法吗?
如有任何帮助,我们将不胜感激。我已经在这个问题上停留了很长时间,并希望得到外界对此事的意见。
您知道如何实现自定义 System.Web.Security.MembershipProvider
吗?您应该能够将此(覆盖 ValidateUser
)与 System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials()
结合使用以针对活动目录进行身份验证。
尝试:
var pc = new PrincipalContext(ContextType.Domain, "example.com", "DC=example,DC=com");
pc.ValidateCredentials(username, password);
只需使用用户名和密码访问 AD,而不是针对您的数据库进行身份验证
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.UserName);
if (user != null && AuthenticateAD(model.UserName, model.Password))
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
public bool AuthenticateAD(string username, string password)
{
using(var context = new PrincipalContext(ContextType.Domain, "MYDOMAIN"))
{
return context.ValidateCredentials(username, password);
}
}
采用你上面的解决方案将我推向了在 MVC6-Beta3 Identityframework7-Beta3 EntityFramework7-Beta3 上对我有用的方向:
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
//
// Check for user existance in Identity Framework
//
ApplicationUser applicationUser = await _userManager.FindByNameAsync(model.eID);
if (applicationUser == null)
{
ModelState.AddModelError("", "Invalid username");
return View(model);
}
//
// Authenticate user credentials against Active Directory
//
bool isAuthenticated = await Authentication.ValidateCredentialsAsync(
_applicationSettings.Options.DomainController,
_applicationSettings.Options.DomainControllerSslPort,
model.eID, model.Password);
if (isAuthenticated == false)
{
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
//
// Signing the user step 1.
//
IdentityResult identityResult
= await _userManager.CreateAsync(
applicationUser,
cancellationToken: Context.RequestAborted);
if(identityResult != IdentityResult.Success)
{
foreach (IdentityError error in identityResult.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View(model);
}
//
// Signing the user step 2.
//
await _signInManager.SignInAsync(applicationUser,
isPersistent: false,
authenticationMethod:null,
cancellationToken: Context.RequestAborted);
return RedirectToLocal(returnUrl);
}
在 ASPNET5 (beta6) 上,想法是使用 CookieAuthentication 和 Identity:您需要在您的 Startup class 中添加:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization();
services.AddIdentity<MyUser, MyRole>()
.AddUserStore<MyUserStore<MyUser>>()
.AddRoleStore<MyRoleStore<MyRole>>()
.AddUserManager<MyUserManager>()
.AddDefaultTokenProviders();
}
在配置部分,添加:
private void ConfigureAuth(IApplicationBuilder app)
{
// Use Microsoft.AspNet.Identity & Cookie authentication
app.UseIdentity();
app.UseCookieAuthentication(options =>
{
options.AutomaticAuthentication = true;
options.LoginPath = new PathString("/App/Login");
});
}
然后,您需要执行:
Microsoft.AspNet.Identity.IUserStore
Microsoft.AspNet.Identity.IRoleStore
Microsoft.AspNet.Identity.IUserClaimsPrincipalFactory
和extend/override:
Microsoft.AspNet.Identity.UserManager
Microsoft.AspNet.Identity.SignInManager
我实际上已经设置了一个示例项目来展示如何做到这一点。 GitHub Link.
我在 beta8 上进行了测试,并且使用了一些小的适配(比如 Context => HttpContext)它也能正常工作。
您可以使用 ClaimTransformation,我今天下午刚刚使用下面的文章和代码让它工作。我正在使用 Window 身份验证访问应用程序,然后根据存储在 SQL 数据库中的权限添加声明。这是一篇很好的文章,应该对你有所帮助。
https://github.com/aspnet/Security/issues/863
综上所述...
services.AddScoped<IClaimsTransformer, ClaimsTransformer>();
app.UseClaimsTransformation(async (context) =>
{
IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return await transformer.TransformAsync(context);
});
public class ClaimsTransformer : IClaimsTransformer
{
private readonly DbContext _context;
public ClaimsTransformer(DbContext dbContext)
{
_context = dbContext;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
System.Security.Principal.WindowsIdentity windowsIdentity = null;
foreach (var i in context.Principal.Identities)
{
//windows token
if (i.GetType() == typeof(System.Security.Principal.WindowsIdentity))
{
windowsIdentity = (System.Security.Principal.WindowsIdentity)i;
}
}
if (windowsIdentity != null)
{
//find user in database by username
var username = windowsIdentity.Name.Remove(0, 6);
var appUser = _context.User.FirstOrDefault(m => m.Username == username);
if (appUser != null)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim("Id", Convert.ToString(appUser.Id)));
/*//add all claims from security profile
foreach (var p in appUser.Id)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(p.Permission, "true"));
}*/
}
}
return await System.Threading.Tasks.Task.FromResult(context.Principal);
}
}