MVC - 混合授权 - OWIN + Windows 授权

MVC - Mixed Auth - OWIN + Windows Auth

我需要同时进行 windows 身份验证和 owin(表单)身份验证,但我无法让它工作。

可能最好的选择是让两个站点使用不同的身份验证方法。

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

发生的错误是:

Request filtering is configured on the Web server to deny the request because the query string is too long.

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

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

有什么想法吗?

更新 1

错误

Request filtering is configured on the Web server to deny the request because the query string is too long.

出现是因为在尝试到达登录页面时出现登录循环。

已解决!

我按照这个例子:MVC5-MixAuth

致谢:Mohammed Younes

更新 1

问题:我需要同时进行匿名身份验证Windows身份验证 启用。 但是当你同时启用它们时,你只能得到 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
{

    /// <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
    }
}

扩展

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



    }
}

备注 您需要 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<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; }
    }
}

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

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

然后你需要配置Local IIS来使用WindowsAuthenticationAnonymousAuthentication.您可以在身份验证模块中执行此操作。

注意如果您没有Windows身份验证请转到控制面板 然后 程序和功能 然后 "Turn Windows features on or off":

select "Internet Information Services" > "World Wide Web" > "Security" and select Windows Authentication.

我没有在您的回答中看到这一点,因此对于正在寻找如何在您的 Owin 管道中捕获 Windows Auth 的任何人,您还可以将以下内容添加到您的 ConfigureAuth 方法中:

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