2015-05-07 14 views
17

Ich muss sowohl Windows-Authentifizierung und owin (Formulare) Authentifizierung haben, aber ich kann es nicht zum Funktionieren bringen.MVC - Gemischte Auth - OWIN + Windows Auth

Wahrscheinlich ist die beste Option, zwei Standorte mit unterschiedlichen Authentifizierungsmethoden zu haben.

Ich fand ein Projekt, das tut, was ich will: MVC5-MixedAuth. Aber es verwendet IISExpress und ich kann es nicht mit dem lokalen IIS arbeiten.

Der Fehler, der auftritt:

Anfrage Filterung wird auf dem Webserver konfiguriert ist, die Anfrage abzulehnen, da der Abfrage-String zu lang ist.

Wenn ich entfernen Sie alle meine ConfigureAuth() Methode innerhalb Startup.Auth.cs es den Fehler nicht werfen, aber ich kann mich nicht einloggen, weil es notwendig ist CookieAuthentication zu tun.

Startup.Auth.cs:

public void ConfigureAuth(IAppBuilder app) 
{ 
    app.CreatePerOwinContext(dbEmployeePortal.Create); 
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); 
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); 

    app.UseCookieAuthentication(new CookieAuthenticationOptions 
    { 
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
     LoginPath = new PathString("/Account/Login"), 
     Provider = new CookieAuthenticationProvider 
     { 
      OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, UserMaster, int> 
      (
        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); 
} 

Jede Idee?

UPDATE 1

Der Fehler

anfordern Filterung auf dem Webserver konfiguriert ist, um die Anfrage abzulehnen, da der Abfrage-String zu lang ist.

erscheint, da eine Login-Schleife auftritt, wenn es versucht, die Anmeldeseite zu erreichen.

+1

Dieser Fehler ist fast immer auf eine unendliche Umleitung zurückzuführen, die durch das Erfordernis von Auth auf der Anmeldeseite verursacht wird. Mit anderen Worten: Der Benutzer muss sich anmelden, damit er mit einer Rückgabe-URL im Querystring auf die Anmeldeseite umgeleitet wird. Die Anmeldeseite selbst erfordert jedoch, dass der Benutzer angemeldet ist, sodass sie zur Anmeldeseite umgeleitet werden (mit einer noch längeren Rücksprung-URL in der Abfragezeichenfolge). Spülen Sie und wiederholen Sie den Vorgang, bis die Abfragezeichenfolge hochgeht. Stellen Sie sicher, dass Sie anonymen Zugriff auf alles zulassen, was auf Ihrer Anmeldeseite ausgeführt wird (untergeordnete Aktionen und solche enthalten). –

+0

Hey @ChrisPratt, ja ich stimme dir zu, es kommt aufgrund einer unendlichen Umleitungsschleife vor. Das Problem ist, dass ich keinen anonymen Zugriff will. Ich möchte die UserId von Windows Auth abrufen und dann nach einem Benutzer mit dieser ID suchen und ihn anmelden. Wenn ich anonyme Authentifizierung erlaube, ist der Benutzer, der angezeigt wird, NT AUTHORITY \ ... –

+0

Nun, wieder, der einzige Grund, der auftreten würde, wenn es etwas über Ihre Anmeldeseite selbst gibt, die erfordert, dass ein Benutzer bereits autorisiert ist. Insofern hat es wahrscheinlich überhaupt nichts damit zu tun, dass Sie gemischte Auth verwenden. Durch das Deaktivieren wird lediglich die Berechtigungsprüfung deaktiviert und die Anmeldeseite kann anonym geladen werden. Sie müssen nur herausfinden, was während des Ladens dieser Aktion autorisiert werden muss. –

Antwort

11

Gelöst!

Ich folgte dem Beispiel: MVC5-MixAuth

Credits: Mohammed Younes

UPDATE 1

Problem: I aktiviert sowohl Anonyme Authentifizierung und Windows-Authentifizierung zu haben brauchte. Aber wenn Sie beide aktivieren, können Sie nur NT AUTHORITY \ IUSR erhalten.

Auflösung: Um den aktuellen Benutzer (mit NTLM-Eingabeaufforderung eingeführt) zu erhalten, müssen wir einen Handler erstellen, der ausgeführt wird, wenn ein Benutzer auf der Anmeldeseite eingeben. Wenn der Benutzer die Anmeldeseite aufruft, wird der Handler die aktuelle Windows-Identität im Browser zwischengespeichert und dann als LogonUserIdentity festgelegt.

Hinweis: Ich brauchte Fenster zuerst Login, zu verwenden, wenn der Benutzer die Anmeldeseite trifft wird es versuchen, die Korrespondent ASP.NET Benutzer zu erhalten.

Handler

using System.Threading.Tasks; 
using System.Web; 
using System.Web.Mvc; 
using System.Web.Routing; 
using Microsoft.AspNet.Identity; 

namespace MixedAuth 
{ 

    /// <summary> 
    /// Managed handler for windows authentication. 
    /// </summary> 
    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 
     /// <summary> 
     /// Executes Windows login action against account controller. 
     /// </summary> 
     /// <param name="context"></param> 
     /// <returns></returns> 
     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); 
     } 

     /// <summary> 
     /// Execute Link Windows login action against account controller. 
     /// </summary> 
     /// <param name="context"></param> 
     /// <returns></returns> 
     private async Task WinLinkLoginAsync(HttpContext context) 
     { 
      var routeData = this.CreateRouteData(Action.Link); 

      await ExecuteController(context, routeData); 
     } 

     /// <summary> 
     /// Executes Windows logoff action against controller. 
     /// </summary> 
     /// <param name="context"></param> 
     /// <returns></returns> 
     private async Task WinLogoffAsync(HttpContext context) 
     { 
      var routeData = this.CreateRouteData(Action.Logoff); 

      await ExecuteController(context, routeData); 
     } 

     /// <summary> 
     /// Executes controller based on route data. 
     /// </summary> 
     /// <param name="context"></param> 
     /// <param name="routeData"></param> 
     /// <returns></returns> 
     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 
    } 
} 

Extensions

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

     /// <summary> 
     /// Registers ignore route for the managed handler. 
     /// </summary> 
     /// <param name="routes"></param> 
     public static void IgnoreWindowsLoginRoute(this RouteCollection routes) 
     { 
      routes.IgnoreRoute(windowsLoginRouteName); 
     } 

     /// <summary> 
     /// By pass all middleware and modules, by setting a fake status code. 
     /// </summary> 
     /// <param name="context"></param> 
     public static void RequestChallenge(this HttpContext context) 
     { 
      context.Response.StatusCode = fakeStatusCode; 
     } 

     /// <summary> 
     /// Invoke on end response only. Replaces the current response status code with 401.2 
     /// </summary> 
     /// <param name="context"></param> 
     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; 
      } 
     } 

     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="handler"></param> 
     /// <param name="action"></param> 
     /// <returns></returns> 
     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; 
     } 


     /// <summary> 
     /// Saves userId to the items collection inside <see cref="HttpContext"/>. 
     /// </summary> 
     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); 
     } 

     /// <summary> 
     /// Reads userId from item collection inside <see cref="HttpContext"/>. 
     /// </summary> 
     /// <remarks>The item will removed before this method returns</remarks> 
     /// <param name="context"></param> 
     /// <returns></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; 
     } 

     /// <summary> 
     /// Returns true if the session contains an entry for userId. 
     /// </summary> 
     public static bool SessionHasUserId(this WindowsLoginHandler handler) 
     { 
      return handler.Context.Session[userIdKey] != null; 
     } 

     /// <summary> 
     /// Save a session-state value with the specified userId. 
     /// </summary> 
     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; 
     } 

     /// <summary> 
     /// Reads userId value from session-state. 
     /// </summary> 
     /// <remarks>The session-state value removed before this method returns.</remarks> 
     /// <param name="session"></param> 
     /// <returns></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; 
     } 


     /// <summary> 
     /// Creates a form for windows login, simulating external login providers. 
     /// </summary> 
     /// <param name="htmlHelper"></param> 
     /// <param name="htmlAttributes"></param> 
     /// <returns></returns> 
     public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object htmlAttributes) 
     { 
      return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes); 
     } 

     /// <summary> 
     /// Creates a form for windows login, simulating external login providers. 
     /// </summary> 
     /// <param name="htmlHelper"></param> 
     /// <param name="htmlAttributes"></param> 
     /// <returns></returns> 
     public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object routeValues, object htmlAttributes) 
     { 
      return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes); 
     } 



    } 
} 

Hinweis Sie müssen AccountController.cs als Teil haben.

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

Dann müssen Sie den Handler hinzuzufügen:

<add name="Windows Login Handler" path="Login" verb="GET,POST" type="MixedAuth.WindowsLoginHandler" preCondition="integratedMode" /> 

Startup.cs

app.CreatePerOwinContext(dbEmployeePortal.Create); 
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); 
app.CreatePerOwinContext<ApplicationSignInManager>(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); 

Dazu brauchen Konfigurieren Sie Lokale IIS zu verwenden WindowsAuthentication und AnonymousAuthentication. Sie können dies im Authentifizierungsmodul tun.

Hinweis Wenn Sie nicht über WindowsAuthentication gehen zu Systemsteuerung dann Programme und Funktionen "Windows-Funktionen ein- oder ausschalten" dann:

wählen Sie "Internet Information Services" > "World Wide Web"> "Sicherheit" und wählen Sie Windows-Authentifizierung.

+0

Wie lautet der Code für GetUserId? (Ich nehme an, es ist eine Erweiterungsmethode.) –

+1

Hey @MatthewRodatus, es ist eine Erweiterung von 'Microsoft.AspNet.Identity' –

+0

Sie können den anderen Zweig der Mohamed Younes: OWIN-MixedAuth. In diesem Fall müssen Sie diesen Code nicht alleine machen. https://github.com/MohammadYounes/OWIN-MixedAuth –

1

Ich habe dies nicht in Ihrer Antwort, so dass für alle, die wie Windows-Auth in Ihrer Owin Pipeline zu erfassen, können Sie auch die folgenden auf Ihre ConfigureAuth Methode hinzufügen:

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