ASP.NET MVC и проверка подлинности в смешанном режиме

У меня есть сценарий, в котором я требую, чтобы пользователи имели возможность аутентифицироваться в веб-приложении ASP.NET MVC, используя либо проверку подлинности Windows, либо проверку подлинности с помощью форм. Если пользователь находится во внутренней сети, они будут использовать проверку подлинности Windows, а если они подключаются извне, они будут использовать проверку подлинности с помощью форм. Я видел довольно много людей, которые задавали вопрос, как мне настроить для этого веб-приложение ASP.NET MVC, но я не нашел полного объяснения.

Кто-нибудь может предоставить подробное объяснение с примерами кода, как это сделать?

Спасибо.

Алан Т


person Alan T    schedule 12.03.2010    source источник


Ответы (4)


Это называется смешанным режимом аутентификации. По сути, вы не можете добиться этого в рамках одного приложения, потому что в IIS после настройки аутентификации Windows для виртуального каталога он больше не будет принимать пользователей из разных доменов. Таким образом, в основном вам нужно иметь два приложения, первое с проверкой подлинности Windows, а второе (основное приложение) с проверкой подлинности с помощью форм. Первое приложение будет состоять из одного адреса, который будет просто перенаправлять в основное приложение путем выдачи билета аутентификации для пользователя домена.

person Darin Dimitrov    schedule 13.03.2010
comment
Спасибо за информацию и ссылку. Я попробую. - person Alan T; 13.03.2010
comment
Это не будет работать в IIS7 в интегрированном режиме: stackoverflow.com/questions/ 289317 / - person Garry English; 10.12.2011
comment
Я тоже это выяснил, Гарри. Я все еще ищу решение для этого, поскольку сейчас у меня есть два приложения MVC, которым потребуется эта функция. - person Jeff Reddy; 03.02.2012

Это можно сделать. Измените конфигурацию, установите приложение / root для использования анонимной проверки подлинности и проверки подлинности с помощью форм ... Таким образом, вы можете настроить смешанную проверку подлинности в одном веб-приложении, но это сложно. Итак, сначала настройте приложение для аутентификации с помощью форм с помощью loginUrl = "~ / WinLogin / WinLogin2.aspx". В MVC маршрутизация переопределяет правила аутентификации, установленные IIS, поэтому необходимо использовать страницу aspx, поскольку IIS может установить аутентификацию для файла. Включите анонимную проверку подлинности и проверку подлинности с помощью форм в корневом веб-приложении. Включите проверку подлинности Windows и отключите анонимную проверку подлинности в корневом каталоге / WinLogin. Добавьте пользовательские страницы ошибок 401 и 401.2 для перенаправления обратно на URL-адрес учетной записи / входа.

Это позволит любому браузеру, поддерживающему сквозной доступ, использовать встроенную проверку подлинности Windows для автоматического входа в систему. В то время как некоторые устройства будут получать запрос на ввод учетных данных (например, iPhone), а другие устройства, такие как Blackberry, перенаправляются на страницу входа.

При этом также создается файл cookie, в котором явно добавляются роли пользователей, и создается общий принцип, позволяющий использовать авторизацию на основе ролей.

в WinLogin2.aspx (в каталоге WinLogin в "корневом" веб-приложении в IIS и настроенном на использование проверки подлинности 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

person Mark    schedule 25.01.2012
comment
Привет - Вы показываете перенаправление файла Redirect401.htm через добавленный скрипт. Разве вы не можете этого добиться, установив обработку ошибок для 401.1 в качестве перенаправления? В IIS это означало бы переключиться на параметр «ответить с перенаправлением 302» для страницы с ошибкой 401. В нем говорится, что нужно использовать абсолютный URL-адрес, но также работает корневой относительный. - person Paul George; 13.06.2012
comment
Будут ли пользователи AD входить в систему автоматически при использовании этого решения? - person Akmal Salikhov; 05.04.2018

Вероятно, это будет в нижней части этого вопроса и никогда не будет найдено, но я смог реализовать то, что было описано в

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

Это было довольно просто и тривиально. Не требовалось несколько приложений или взлома файлов cookie, просто расширение FormsAuthModule и внесение некоторых изменений в web.config.

person Luke    schedule 22.08.2013
comment
Я думаю, что упомянутый вами сценарий отличается от рассматриваемого сценария. двухуровневая аутентификация требует, чтобы пользователь прошел аутентификацию в обоих окнах, а также с помощью форм. Однако мы требуем, чтобы в зависимости от типа пользователя (интрасеть / Интернет) они аутентифицировались либо с помощью окон, либо с помощью аутентификации на основе форм. - person Samra; 20.09.2017

Я знаю, что это старый пост - но в Интернете все живет вечно!

В любом случае мне пришлось перенести старый веб-сайт с IIS6 на IIS8. Это веб-сайт WebForms, но я предполагаю, что это очень простое решение такое же.

Я получил сообщение об ошибке: невозможно преобразовать объект типа System.Security.Principal.WindowsIdentity в тип System.Web.Security.FormsIdentity.

Все, что я сделал, - это создал новый пул приложений для веб-сайта. При его создании я установил для режима управляемого конвейера значение «Классический». (Подробнее читайте здесь - http://www.hanselman.com/blog/MovingOldAppsExhyys6ToClassicpx/ >) Не забудьте настроить пул приложений веб-сайта на новый пул, который вы только что создали.

person jagdipa    schedule 12.08.2014