web-dev-qa-db-ja.com

MVC-混合認証-OWIN + Windows認証

Windows認証とowin(フォーム)認証の両方が必要ですが、機能しません。

おそらく最良のオプションは、異なる認証方法を持つ2つのサイトを持つことです。

私がやりたいプロジェクトを見つけました: MVC5-MixedAuth 。しかし、IISExpressを使用しており、ローカルIISで動作させることはできません。

発生するエラーは次のとおりです。

クエリ文字列が長すぎるため、要求を拒否するようにWebサーバーで要求フィルターが構成されています。

すべてのConfigureAuth()メソッドをStartup.Auth.csから削除すると、エラーはスローされませんが、できますtCookieAuthenticationを行う必要があるため、ログインします。

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サーバーで要求フィルターが構成されています。

ログインページに到達しようとするとログインループが発生するためです。

21
Leandro Soares

解決済み!

例に従いました: MVC5-MixAuth

クレジット: Mohammed Younes

更新1

問題:匿名認証Windows認証の両方を有効にする必要がありました。ただし、両方を有効にすると、NT AUTHORITY\IUSRのみを取得できます。

解決策:現在のユーザー(NTLMプロンプトで導入)を取得するには、ユーザーがログインページで入力したときに実行されるハンドラーを作成する必要があります。ユーザーがログインページにアクセスすると、ハンドラーはブラウザーにキャッシュされている現在のWindows IDを取得し、LogonUserIdentityとして設定します。

注:windows first loginを使用する必要がありました。ユーザーがログインページにアクセスすると、対応する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);

次に、WindowsAuthenticationおよびAnonymousAuthenticationを使用するように、ローカルIISを構成する必要があります。これは認証モジュールで実行できます。

持っていない場合WindowsAuthenticationに移動しますコントロールパネルその後プログラムおよび機能その後、「Windowsの機能をオンまたはオフにする」:

[インターネットインフォメーションサービス]> [World Wide Web]> [セキュリティ]を選択し、[Windows認証]を選択します。

14
Leandro Soares

私はあなたの答えにはこれを見ていませんでしたので、OwinパイプラインでWindows Authをキャプチャする方法を探している人は、ConfigureAuthメソッドに次を追加することもできます:

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