MVC - 混合身份验证 - OWIN + Windows身份验证

时间:2015-05-07 15:40:36

标签: c# asp.net-mvc asp.net-mvc-5 owin windows-authentication

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

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

我发现了一个能够实现我想要的项目: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>(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

错误

  

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

出现

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

2 个答案:

答案 0 :(得分:14)

解决!

我按照示例: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);

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

注意如果您没有 WindowsAuthentication ,请转到控制面板,然后程序和功能,然后&#34;打开或关闭Windows功能&#34;:

  

选择&#34;互联网信息服务&#34; &GT; &#34;万维网&#34; &GT; &#34;安全&#34;   并选择Windows身份验证。

答案 1 :(得分:1)

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

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