【问题标题】:ASP.NET MVC and mixed mode authenticationASP.NET MVC 和混合模式身份验证
【发布时间】:2011-01-26 19:58:37
【问题描述】:

我有一个场景,我要求用户能够使用 Windows 身份验证或 Forms 身份验证对 ASP.NET MVC Web 应用程序进行身份验证。如果用户在内部网络上,他们将使用 Windows 身份验证,如果他们在外部连接,他们将使用 Forms 身份验证。我看到很多人问我如何为此配置一个 ASP.NET MVC Web 应用程序,但我还没有找到完整的解释。

请有人提供详细的解释和代码示例,说明如何做到这一点?

谢谢。

艾伦·T

【问题讨论】:

    标签: asp.net-mvc mixed-mode


    【解决方案1】:

    这称为mixed authentication mode。基本上,您无法在单个 应用程序中实现这一点,因为在 IIS 中,一旦您为虚拟目录设置了 Windows 身份验证,它将不再接受来自不同域的用户。所以基本上你需要有两个应用程序,第一个使用 Windows 身份验证,第二个(主应用程序)使用 Forms 身份验证。第一个应用程序将包含一个地址,该地址将通过为域用户颁发身份验证票证来简单地重定向到主应用程序。

    【讨论】:

    • 感谢您的信息和链接。我试试看。
    • 这在使用集成模式的 IIS7 中不起作用:stackoverflow.com/questions/289317/…
    • 我也发现了这一点,Garry。我仍在为此寻找解决方案,因为我现在有两个需要此功能的 MVC 应用程序。
    【解决方案2】:

    这是可以做到的。颠倒配置,设置 app/root 使用匿名和表单身份验证...这样,您可以在同一个 Web 应用程序中配置混合身份验证,但这很棘手。因此,首先,使用 loginUrl="~/WinLogin/WinLogin2.aspx" 为您的应用程序配置表单身份验证。在MVC中,路由会覆盖IIS设置的认证规则,所以需要使用aspx页面,因为IIS可以对文件设置认证。在根 Web 应用程序上启用匿名和表单身份验证。在 root/WinLogin 目录中启用 Windows 身份验证并禁用匿名身份验证。添加自定义 401 和 401.2 错误页面以重定向回帐户/登录 URL。

    这将允许任何能够直通的浏览器使用 Windows 集成身份验证来自动登录。虽然某些设备会被提示输入凭据(如 iPhone),而其他设备(如黑莓)会被重定向到登录页面。

    这还会创建一个明确添加用户角色的 cookie,并创建一个通用原则,以便可以使用基于角色的授权。

    在 WinLogin2.aspx 中(在 IIS 的“根”Web 应用程序下的 WinLogin 目录中,并配置为使用 Windows 身份验证、禁用匿名和启用表单(因为无法关闭...注意启用时 IIS 会报错) windows身份验证,忽略):

    var logonUser = Request.ServerVariables["LOGON_USER"];
    if (!String.IsNullOrWhiteSpace(logonUser))
    {
        if (logonUser.Split('\\').Length > 1)
        {
            var domain = logonUser.Split('\\')[0];
            var username = logonUser.Split('\\')[1];
    
            var timeout = 30;
    
            var encTicket = CreateTicketWithSecurityGroups(false, username, domain, timeout);
    
            var authCookie = new HttpCookie(".MVCAUTH", encTicket) { HttpOnly = true };
            Response.Cookies.Add(authCookie);
        }
        //else
        //{
        // this is a redirect due to returnUrl being WinLogin page, in which logonUser will no longer have domain attached
        // ignore as forms ticket should already exist
        //}
    
        string returnUrl = Request.QueryString["ReturnUrl"];
    
        if (returnUrl.IsEmpty())
        {
            Response.Redirect("~/");
        }
        else
        {
            Response.Redirect(returnUrl);
        }
    }
    
    public static string CreateTicketWithSecurityGroups(bool rememberMe, string username, string domain, int timeout)
    {
        using (var context = new PrincipalContext(ContextType.Domain, domain))
        {
            using (var principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username))
            {
                var securityGroups = String.Join(";", principal.GetAuthorizationGroups());
    
                var ticket =
                    new FormsAuthenticationTicket(1,
                                                    username,
                                                    DateTime.UtcNow,
                                                    DateTime.UtcNow.AddMinutes(timeout),
                                                    rememberMe,
                                                    securityGroups,
                                                    "/");
    
                string encTicket = FormsAuthentication.Encrypt(ticket);
                return encTicket;
            }
        }
    }
    

    在 IIS 7.5 中,点击错误页面,将 401 页面设置为 Redirect401.htm 文件的文件路径,代码如下:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <script>
          window.location.assign('../Account/Signin');
        </script>
    </head>
    <body>
    </body>
    </html>
    

    在 AccountController...

    public ActionResult SignIn()
    {
        return View(new SignInModel());
    }
    
    //
    // POST: /Account/SignIn
    [HttpPost]
    public ActionResult SignIn(SignInModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (Membership.ValidateUser(model.UserName, model.Password))
            {
                string encTicket = CreateTicketWithSecurityGroups(model.RememberMe,  model.UserName, model.Domain, FormsAuthentication.Timeout.Minutes);
    
                Response.Cookies.Add(new HttpCookie(".MVCAUTH", encTicket));
    
                //var returnUrl = "";
                for (var i = 0; i < Request.Cookies.Count; i++)
                {
                    HttpCookie cookie = Request.Cookies[i];
                    if (cookie.Name == ".MVCRETURNURL")
                    {
                        returnUrl = cookie.Value;
                        break;
                    }
                }
    
                if (returnUrl.IsEmpty())
                {
                    return Redirect("~/");
                }
    
                return Redirect(returnUrl);
            }
    
            ModelState.AddModelError("Log In Failure", "The username/password combination is invalid");
        }
    
        return View(model);
    }
    
    //
    // GET: /Account/SignOut
    public ActionResult SignOut()
    {
        FormsAuthentication.SignOut();
    
        if (Request.Cookies[".MVCRETURNURL"] != null)
        {
            var returnUrlCookie = new HttpCookie(".MVCRETURNURL") { Expires = DateTime.Now.AddDays(-1d) };
            Response.Cookies.Add(returnUrlCookie);
        }
    
        // Redirect back to sign in page so user can 
        //   sign in with different credentials
    
        return RedirectToAction("SignIn", "Account");
    }
    

    在 global.asax 中:

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        try
        {
            bool cookieFound = false;
    
            HttpCookie authCookie = null;
    
            for (int i = 0; i < Request.Cookies.Count; i++)
            {
                HttpCookie cookie = Request.Cookies[i];
                if (cookie.Name == ".MVCAUTH")
                {
                    cookieFound = true;
                    authCookie = cookie;
                    break;
                }
            }
    
            if (cookieFound)
            {
                // Extract the roles from the cookie, and assign to our current principal, which is attached to the HttpContext.
                FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
                HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), ticket.UserData.Split(';'));
            }
        }
        catch (Exception ex)
        {
            throw;
        }
    }
    
    
    protected void Application_AuthenticateRequest()
    {
        var returnUrl = Request.QueryString["ReturnUrl"];
        if (!Request.IsAuthenticated && !String.IsNullOrWhiteSpace(returnUrl))
        {
            var returnUrlCookie = new HttpCookie(".MVCRETURNURL", returnUrl) {HttpOnly = true};
            Response.Cookies.Add(returnUrlCookie);
        }
    }
    

    web.config

    <system.web>
      <!--<authorization>
        <deny users="?"/>
      </authorization>-->
      <authentication mode="Forms">
        <forms name=".MVCAUTH" loginUrl="~/WinLogin/WinLogin2.aspx" timeout="30" enableCrossAppRedirects="true"/>
      </authentication>
      <membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
        <providers>
          <add
               name="AspNetActiveDirectoryMembershipProvider"
               type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
               connectionStringName="ADService" connectionProtection="Secure" enablePasswordReset="false" enableSearchMethods="true" requiresQuestionAndAnswer="true"
               applicationName="/" description="Default AD connection" requiresUniqueEmail="false" clientSearchTimeout="30" serverSearchTimeout="30"
               attributeMapPasswordQuestion="department" attributeMapPasswordAnswer="division" attributeMapEmail="mail" attributeMapUsername="sAMAccountName"
               maxInvalidPasswordAttempts="5" passwordAttemptWindow="10" passwordAnswerAttemptLockoutDuration="30" minRequiredPasswordLength="7"
               minRequiredNonalphanumericCharacters="1" />
        </providers>
      </membership>
      <machineKey decryptionKey="..." validationKey="..." />
    </system.web>
    <connectionStrings>
      <add name="ADService" connectionString="LDAP://SERVER:389"/>
    </connectionStrings>
    

    http://msdn.microsoft.com/en-us/library/ms972958.aspx的信用

    【讨论】:

    • 嗨 - 您通过添加的脚本显示 Redirect401.htm 文件重定向。您不能通过将 401.1 的错误处理设置为重定向来实现这一点吗?在 IIS 中,这意味着将 401 错误页面更改为“使用 302 重定向响应”选项。据说使用绝对 url,但根相对也可以。
    • 使用这个方案AD用户会自动登录吗?
    【解决方案3】:

    这可能会出现在这个问题的底部并且永远不会被发现,但我能够实现在

    中描述的内容

    http://mvolo.com/iis-70-twolevel-authentication-with-forms-authentication-and-windows-authentication/

    这很容易和微不足道。不需要多个应用程序或 cookie hack,只需扩展 FormsAuthModule 并进行一些 web.config 更改。

    【讨论】:

    • 我认为您提到的场景与所讨论的场景不同。两级身份验证要求用户同时通过 Windows 身份验证和表单身份验证。但是,我们要求根据用户的类型(内联网/互联网),他们应该通过 Windows 或基于表单的身份验证来进行身份验证
    【解决方案4】:

    我知道这是一篇旧帖子 - 但互联网上的一切都永远存在!

    无论如何,我不得不将旧网站从 IIS6 移动到 IIS8。这是一个 WebForms 网站,但我认为这个非常简单的解决方案是相同的。

    我收到错误:无法将“System.Security.Principal.WindowsIdentity”类型的对象转换为“System.Web.Security.FormsIdentity”类型。

    我所做的只是为网站创建一个新的应用程序池。创建它时,我将托管管道模式设置为“经典”。 (在此处阅读更多信息 - http://www.hanselman.com/blog/MovingOldAppsFromIIS6ToIIS8AndWhyClassicModeExists.aspx)不要忘记将网站的应用程序池设置为您刚刚创建的新池。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-23
      • 1970-01-01
      • 1970-01-01
      • 2015-07-18
      • 2010-12-20
      • 2015-04-08
      相关资源
      最近更新 更多