asp.net-mvc ASP.NET MVC 和混合模式身份验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2432845/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:11:58  来源:igfitidea点击:

ASP.NET MVC and mixed mode authentication

asp.net-mvcmixed-mode

提问by Alan T

I have a scenario whereby I require users to be able to authenticate against an ASP.NET MVC web application using either Windows authentication or Forms authentication. If the user is on the internal network they will use Windows authentication and if they are connecting externally they will use Forms authentication. I've seen quite a few people asking the question how do I configure an ASP.NET MVC web application for this, but I haven't found a complete explanation.

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

Please can someone provide a detailed explanation, with code examples, on how this would be done?

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

Thanks.

谢谢。

Alan T

艾伦

采纳答案by Darin Dimitrov

This is called mixed authentication mode. Basically you cannot achieve this within a singleapplication because in IIS once you set up Windows authentication for a virtual directory it will no longer accept users from different domains. So basically you need to have two applications, the first with Windows Authentication and the second (the main application) using Forms authentication. The first application will consist of a single address which will simply redirect to the main application by issuing an authentication ticket for the domain user.

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

回答by Mark

This can be done. Reverse the configuration, set the app/root to use Anonymous and Forms Authentication... In this way, you can configure mixed authentication within the same web application, but it is tricky. So first, configure you app for Forms Authentication with loginUrl="~/WinLogin/WinLogin2.aspx". In MVC, routing overrides authentication rules set by IIS, so need to use an aspx page, as IIS can set authentication on the file. Enable Anonymous and Forms Authentication on the root web application. Enable Windows Authentication and disable anonymous authentication in root/WinLogin directory. Add custom 401 and 401.2 error pages to redirect back to the Account/Signin URL.

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

This will allow any browser capable of pass-through to use windows integrated authentication to auto signin. While some devices will get prompted for credentials (like iPhone) and other devices like blackberry redirected to signin page.

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

This also creates a cookie explicitly adding the users roles and creates a Generic principle so that role-based authorization can be used.

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

in WinLogin2.aspx (in WinLogin directory under "root" web application in IIS, and configured to use Windows Authentication, Anonymous disabled, and Forms enabled (as can't turn off...note IIS will complain when you enable windows authentication, just ignore) :

在 WinLogin2.aspx 中(在 IIS 的“root”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;
        }
    }
}

In IIS 7.5, click Error Pages, set the 401 page to File path of Redirect401.htm file, with this code:

在IIS 7.5中,点击Error Pages,将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>

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

In global.asax:

在 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>

Credit owed to http://msdn.microsoft.com/en-us/library/ms972958.aspx

归功于http://msdn.microsoft.com/en-us/library/ms972958.aspx

回答by Luke

This will probably live at the bottom of this question and never be found but I was able to implement what was described at

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

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

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

It was quite easy and trivial. Didn't require multiple applications or cookie hacks, just extending the FormsAuthModule and making some web.config changes.

这是非常简单和微不足道的。不需要多个应用程序或 cookie hack,只需扩展 FormsAuthModule 并进行一些 web.config 更改。

回答by jagdipa

I know this is an old post - but everything lives forever on the internet!

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

Anyway, I had to move an old website from IIS6 to IIS8. This is a WebForms website, but I assume this very simple solution is the same.

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

I received the error : Unable to cast object of type 'System.Security.Principal.WindowsIdentity' to type 'System.Web.Security.FormsIdentity'.

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

All I did was create a new application pool for the website. When creating this, I set the Managed pipeline mode to 'Classic'. (Read more here - http://www.hanselman.com/blog/MovingOldAppsFromIIS6ToIIS8AndWhyClassicModeExists.aspx) Dont forget to set the website's application pool to the new pool you just created.

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