26

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

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

谢谢。

艾伦 T

4

4 回答 4

18

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

于 2010-03-13T09:08:51.437 回答
18

这是可以做到的。颠倒配置,设置 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 身份验证、禁用匿名和启用表单(因为无法关闭...注意当您启用 Windows 身份验证时 IIS 会报错,直接无视(好了) :

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>

在帐户控制器...

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

网络配置

<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

于 2012-01-25T19:13:54.687 回答
4

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

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

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

于 2013-08-22T18:47:38.097 回答
0

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

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

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

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

于 2014-08-12T08:45:47.017 回答