MVC – 混合身份validation – OWIN + Windows身份validation

我需要同时具有Windows身份validation和owin(表单)身份validation,但我无法让它工作。

可能最好的选择是让两个站点具有不同的身份validation方法。

我找到了一个能满足我需求的项目: MVC5-MixedAuth 。 但它使用IISExpress,我无法让它与本地IIS一起使用。

发生的错误是:

在Web服务器上配置请求筛选以拒绝请求,因为查询字符串太长。

如果我删除Startup.Auth.cs中的所有ConfigureAuth()方法,它不会抛出错误但我无法登录,因为它需要进行CookieAuthentication

Startup.Auth.cs:

public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(dbEmployeePortal.Create); app.CreatePerOwinContext(ApplicationUserManager.Create); app.CreatePerOwinContext(ApplicationSignInManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity ( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager), getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())) ) } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); } 

任何的想法?

更新1

错误

在Web服务器上配置请求筛选以拒绝请求,因为查询字符串太长。

出现是因为当它尝试访问登录页面时发生登录循环。

解决!

我按照这个例子: MVC5-MixAuth

致谢: Mohammed Younes

更新1

问题:我需要同时启用匿名身份validationWindows身份validation 。 但是当你同时启用它们时,你只能获得NT AUTHORITY \ IUSR

解决方案:要获取当前用户(使用NTLM提示引入),我们需要创建一个在用户进入登录页面时执行的处理程序。 当用户点击登录页面时,处理程序将获得在浏览器中缓存的当前Windows标识,然后设置为LogonUserIdentity

注意:我需要先使用Windows登录,当用户点击登录页面时,它会尝试获取对应的ASP.NET用户。

处理器

 using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.AspNet.Identity; namespace MixedAuth { ///  /// Managed handler for windows authentication. ///  public class WindowsLoginHandler : HttpTaskAsyncHandler, System.Web.SessionState.IRequiresSessionState { public HttpContext Context { get; set; } public override async Task ProcessRequestAsync(HttpContext context) { this.Context = context; //if user is already authenticated, LogonUserIdentity will be holding the current application pool identity. //to overcome this: //1. save userId to session. //2. log user off. //3. request challenge. //4. log user in. if (context.User.Identity.IsAuthenticated) { this.SaveUserIdToSession(context.User.Identity.GetUserId()); await WinLogoffAsync(context); context.RequestChallenge(); } else if (!context.Request.LogonUserIdentity.IsAuthenticated) { context.RequestChallenge(); } else { // true: user is trying to link windows login to an existing account if (this.SessionHasUserId()) { var userId = this.ReadUserIdFromSession(); this.SaveUserIdToContext(userId); await WinLinkLoginAsync(context); } else // normal login. await WinLoginAsync(context); } } #region helpers ///  /// Executes Windows login action against account controller. ///  ///  ///  private async Task WinLoginAsync(HttpContext context) { var routeData = this.CreateRouteData(Action.Login); routeData.Values.Add("returnUrl", context.Request["returnUrl"]); routeData.Values.Add("userName", context.Request.Form["UserName"]); await ExecuteController(context, routeData); } ///  /// Execute Link Windows login action against account controller. ///  ///  ///  private async Task WinLinkLoginAsync(HttpContext context) { var routeData = this.CreateRouteData(Action.Link); await ExecuteController(context, routeData); } ///  /// Executes Windows logoff action against controller. ///  ///  ///  private async Task WinLogoffAsync(HttpContext context) { var routeData = this.CreateRouteData(Action.Logoff); await ExecuteController(context, routeData); } ///  /// Executes controller based on route data. ///  ///  ///  ///  private async Task ExecuteController(HttpContext context, RouteData routeData) { var wrapper = new HttpContextWrapper(context); MvcHandler handler = new MvcHandler(new RequestContext(wrapper, routeData)); IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler)handler); await Task.Factory.FromAsync(asyncHandler.BeginProcessRequest, asyncHandler.EndProcessRequest, context, null); } #endregion } } 

扩展

 using System; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; namespace MixedAuth { public enum Action { Login, Link, Logoff }; public static class MixedAuthExtensions { const string userIdKey = "windows.userId"; //http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html const int fakeStatusCode = 418; const string controllerName = "Account"; const string loginActionName = "WindowsLogin"; const string linkActionName = "LinkWindowsLogin"; const string logoffActionName = "WindowsLogoff"; const string windowsLoginRouteName = "Windows/Login"; public static void RegisterWindowsAuthentication(this MvcApplication app) { app.EndRequest += (object sender, EventArgs e) => { HttpContext.Current.ApplyChallenge(); }; } ///  /// Registers ignore route for the managed handler. ///  ///  public static void IgnoreWindowsLoginRoute(this RouteCollection routes) { routes.IgnoreRoute(windowsLoginRouteName); } ///  /// By pass all middleware and modules, by setting a fake status code. ///  ///  public static void RequestChallenge(this HttpContext context) { context.Response.StatusCode = fakeStatusCode; } ///  /// Invoke on end response only. Replaces the current response status code with 401.2 ///  ///  public static void ApplyChallenge(this HttpContext context) { if (context.Response.StatusCode == fakeStatusCode) { context.Response.StatusCode = 401; context.Response.SubStatusCode = 2; //http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx //context.Response.TrySkipIisCustomErrors = true; } } ///  /// ///  ///  ///  ///  public static RouteData CreateRouteData(this WindowsLoginHandler handler, Action action) { RouteData routeData = new RouteData(); routeData.RouteHandler = new MvcRouteHandler(); switch (action) { case Action.Login: routeData.Values.Add("controller", controllerName); routeData.Values.Add("action", loginActionName); break; case Action.Link: routeData.Values.Add("controller", controllerName); routeData.Values.Add("action", linkActionName); break; case Action.Logoff: routeData.Values.Add("controller", controllerName); routeData.Values.Add("action", logoffActionName); break; default: throw new NotSupportedException(string.Format("unknonw action value '{0}'.", action)); } return routeData; } ///  /// Saves userId to the items collection inside . ///  public static void SaveUserIdToContext(this WindowsLoginHandler handler, string userId) { if (handler.Context.Items.Contains(userIdKey)) throw new ApplicationException("Id already exists in context."); handler.Context.Items.Add("windows.userId", userId); } ///  /// Reads userId from item collection inside . ///  /// The item will removed before this method returns ///  ///  public static int ReadUserId(this HttpContextBase context) { if (!context.Items.Contains(userIdKey)) throw new ApplicationException("Id not found in context."); int userId = Convert.ToInt32(context.Items[userIdKey] as string); context.Items.Remove(userIdKey); return userId; } ///  /// Returns true if the session contains an entry for userId. ///  public static bool SessionHasUserId(this WindowsLoginHandler handler) { return handler.Context.Session[userIdKey] != null; } ///  /// Save a session-state value with the specified userId. ///  public static void SaveUserIdToSession(this WindowsLoginHandler handler, string userId) { if (handler.SessionHasUserId()) throw new ApplicationException("Id already exists in session."); handler.Context.Session[userIdKey] = userId; } ///  /// Reads userId value from session-state. ///  /// The session-state value removed before this method returns. ///  ///  public static string ReadUserIdFromSession(this WindowsLoginHandler handler) { string userId = handler.Context.Session[userIdKey] as string; if (string.IsNullOrEmpty(userIdKey)) throw new ApplicationException("Id not found in session."); handler.Context.Session.Remove(userIdKey); return userId; } ///  /// Creates a form for windows login, simulating external login providers. ///  ///  ///  ///  public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object htmlAttributes) { return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes); } ///  /// Creates a form for windows login, simulating external login providers. ///  ///  ///  ///  public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object routeValues, object htmlAttributes) { return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes); } } } 

注意您需要将AccountController.cs作为部分。

AccountController.Windows.cs

 using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.AspNet.Identity; using MixedAuth; namespace EmployeePortal.Web.Controllers { [Authorize] public partial class AccountController : BaseController { // // POST: /Account/WindowsLogin [AllowAnonymous] [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)] public ActionResult WindowsLogin(string userName, string returnUrl) { if (!Request.LogonUserIdentity.IsAuthenticated) { return RedirectToAction("Login"); } var loginInfo = GetWindowsLoginInfo(); // Sign in the user with this external login provider if the user already has a login var user = UserManager.Find(loginInfo); if (user != null) { SignIn(user, isPersistent: false); return RedirectToLocal(returnUrl); } else { return RedirectToAction("Login", new RouteValueDictionary(new { controller = "Account", action = "Login", returnUrl = returnUrl })); } } // // POST: /Account/WindowsLogOff [HttpPost] [ValidateAntiForgeryToken] public void WindowsLogOff() { AuthenticationManager.SignOut(); } // // POST: /Account/LinkWindowsLogin [AllowAnonymous] [HttpPost] public async Task LinkWindowsLogin() { int userId = HttpContext.ReadUserId(); //didn't get here through handler if (userId <= 0) return RedirectToAction("Login"); HttpContext.Items.Remove("windows.userId"); //not authenticated. var loginInfo = GetWindowsLoginInfo(); if (loginInfo == null) return RedirectToAction("Manage"); //add linked login var result = await UserManager.AddLoginAsync(userId, loginInfo); //sign the user back in. var user = await UserManager.FindByIdAsync(userId); if (user != null) await SignInAsync(user, false); if (result.Succeeded) return RedirectToAction("Manage"); return RedirectToAction("Manage", new { Message = ManageMessageId.Error }); } #region helpers private UserLoginInfo GetWindowsLoginInfo() { if (!Request.LogonUserIdentity.IsAuthenticated) return null; return new UserLoginInfo("Windows", Request.LogonUserIdentity.User.ToString()); } #endregion } public class WindowsLoginConfirmationViewModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } } } 

然后,您需要添加处理程序:

  

Startup.cs

 app.CreatePerOwinContext(dbEmployeePortal.Create); app.CreatePerOwinContext(ApplicationUserManager.Create); app.CreatePerOwinContext(ApplicationSignInManager.Create); PathString path = new PathString("/Account/Login"); if (GlobalExtensions.WindowsAuthActive) path = new PathString("/Windows/Login"); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, //LoginPath = new PathString("/Account/Login") LoginPath = path }); // Use a cookie to temporarily store information about a user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); 

然后,您需要配置本地IIS以使用WindowsAuthenticationAnonymousAuthentication 。 您可以在身份validation模块中执行此操作。

注意如果您没有Windows身份validation,请转到“ 控制面板然后转到“ 程序和function然后“打开或关闭Windowsfunction”:

选择“Internet信息服务”>“万维网”>“安全”,然后选择Windows身份validation。

我没有在你的答案中看到这一点,所以对于任何想要在你的Owin管道中捕获Windows Auth的人,你也可以在ConfigureAuth方法中添加以下内容:

 public void ConfigureAuth(IAppBuilder app) { HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"]; listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication; }