Отключение проверки подлинности ASP.Net WebForms для одного подкаталога - PullRequest
15 голосов
/ 06 января 2011

У меня есть крупное корпоративное приложение, содержащее страницы WebForms и MVC. В нем есть параметры аутентификации и авторизации, которые я не хочу менять.

Аутентификация WebForms настраивается в файле web.config:

 <authentication mode="Forms">
  <forms blah... blah... blah />
 </authentication>

 <authorization>
  <deny users="?" />
 </authorization>

Пока довольно стандартно. У меня есть служба REST, которая является частью этого большого приложения, и я хочу вместо этого использовать HTTP-аутентификацию для этой службы.

Таким образом, когда пользователь пытается получить данные JSON из службы REST, он возвращает статус HTTP 401 и заголовок WWW-Authenticate. Если они отвечают правильно сформированным ответом HTTP Authorization, это позволяет им войти.

Проблема в том, что WebForms переопределяет это на низком уровне - если вы возвращаете 401 (Неавторизованный), он переопределяет это с 302 (перенаправление на страницу входа). Это нормально в браузере, но бесполезно для службы REST.

Я хочу отключить настройку аутентификации в файле web.config, переопределив папку rest:

 <location path="rest">
  <system.web>
   <authentication mode="None" />
   <authorization><allow users="?" /></authorization>
  </system.web>
 </location>

Бит авторизации работает нормально, но строка аутентификации (<authentication mode="None" />) вызывает исключение:

Ошибка использовать раздел, зарегистрированный как allowDefinition = 'MachineToApplication' за пределами уровня приложения.

Я настраиваю это на уровне приложения - он находится в корневом каталоге web.config - и эта ошибка относится к web.configs в подкаталогах.

Как мне переопределить аутентификацию , чтобы все остальное на сайте использовало аутентификацию WebForms, а этот каталог не использовал ни одного?

Это похоже на другой вопрос: 401 код ответа для запросов json с ASP.NET MVC , но я не ищу того же решения - я не хочу просто удалять аутентификацию WebForms и добавление нового пользовательского кода в глобальном масштабе, есть большой риск и работа. Я хочу изменить только один каталог в конфигурации.

Обновление

Я хочу настроить одно веб-приложение, и в нем я хочу, чтобы все страницы WebForms и представления MVC использовали аутентификацию WebForms. Я хочу, чтобы в одном каталоге использовалась базовая аутентификация HTTP.

Обратите внимание, что я говорю об аутентификации, а не об авторизации. Я хочу, чтобы вызовы REST приходили с именем пользователя и паролем в заголовке HTTP, и я хочу, чтобы страницы WebForm и MVC приходили с файлом cookie аутентификации из .Net - в любом случае авторизация выполняется для нашей БД.

Я не хочу переписывать аутентификацию WebForms и катить свои собственные куки - кажется нелепым, что это единственный способ добавить авторизованную HTTP-службу REST в приложение.

Я не могу добавить дополнительное приложение или виртуальный каталог - оно должно быть как одно приложение.

Ответы [ 7 ]

7 голосов
/ 06 января 2011

Если "rest" - это просто папка в вашем корне, вы почти у цели: удалите строку аутентификации, т.е.

<location path="rest">
  <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
  </system.web>
 </location>

В качестве альтернативы вы можете добавить web.config в вашу папку rest и просто иметь это:

<system.web>
     <authorization>
          <allow users="*" />
     </authorization>
</system.web>

Проверьте этот один.

4 голосов
/ 21 января 2012

У меня возникла точно такая же проблема, следующая статья указала мне правильное направление: http://msdn.microsoft.com/en-us/library/aa479391.aspx

MADAM делает именно то, что вам нужно, в частности, вы можете настроить FormsAuthenticationDispositionModule для отключения формаутентификация "обман" и не дает ей изменить код ответа с 401 на 302. Это должно привести к тому, что ваш остальной клиент получит правильный запрос аутентификации.

MADAM Страница загрузки: http://www.raboof.com/projects/madam/

В моем случае REST-вызовы выполняются для контроллеров (это приложение на основе MVC) в области «API».Дискриминатор MADAM устанавливается со следующей конфигурацией:

<formsAuthenticationDisposition>
  <discriminators all="1">
    <discriminator type="Madam.Discriminator">
      <discriminator
          inputExpression="Request.Url"
          pattern="api\.*" type="Madam.RegexDiscriminator" />
    </discriminator>
  </discriminators>
</formsAuthenticationDisposition>

Затем все, что вам нужно сделать, это добавить модуль MADAM в ваш web.config

<modules runAllManagedModulesForAllRequests="true">
  <remove name="WebDAVModule" /> <!-- allow PUT and DELETE methods -->
  <add name="FormsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionModule, Madam" />
</modules>

Не забудьте добавить действительные разделы в файл web.config (так что мне не удалось вставить код), вы можете получить пример из веб-проекта при загрузке.

С этимПри установке любых запросов к URL-адресам, начинающимся с «API /», будет получен ответ 401 вместо 301, полученных при проверке подлинности с помощью форм.

4 голосов
/ 18 января 2011

Я обошел этот беспорядочный путь - подделав проверку подлинности с помощью форм в global.asax для всех существующих страниц.

Я до сих пор не совсем полностью это работаю, но это идетвот так:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // lots of existing web.config controls for which webforms folders can be accessed
    // read the config and skip checks for pages that authorise anon users by having
    // <allow users="?" /> as the top rule.

    // check local config
    var localAuthSection = ConfigurationManager.GetSection("system.web/authorization") as AuthorizationSection;

    // this assumes that the first rule will be <allow users="?" />
    var localRule = localAuthSection.Rules[0];
    if (localRule.Action == AuthorizationRuleAction.Allow &&
        localRule.Users.Contains("?"))
    {
        // then skip the rest
        return;
    }

    // get the web.config and check locations
    var conf = WebConfigurationManager.OpenWebConfiguration("~");
    foreach (ConfigurationLocation loc in conf.Locations)
    {
        // find whether we're in a location with overridden config
        if (this.Request.Path.StartsWith(loc.Path, StringComparison.OrdinalIgnoreCase) ||
            this.Request.Path.TrimStart('/').StartsWith(loc.Path, StringComparison.OrdinalIgnoreCase))
        {
            // get the location's config
            var locConf = loc.OpenConfiguration();
            var authSection = locConf.GetSection("system.web/authorization") as AuthorizationSection;
            if (authSection != null)
            {
                // this assumes that the first rule will be <allow users="?" />
                var rule = authSection.Rules[0];
                if (rule.Action == AuthorizationRuleAction.Allow &&
                    rule.Users.Contains("?"))
                {
                    // then skip the rest
                    return;
                }
            }
        }
    }

    var cookie = this.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (cookie == null ||
        string.IsNullOrEmpty(cookie.Value))
    {
        // no or blank cookie
        FormsAuthentication.RedirectToLoginPage();
    }

    // decrypt the 
    var ticket = FormsAuthentication.Decrypt(cookie.Value);
    if (ticket == null ||
        ticket.Expired)
    {
        // invalid cookie
        FormsAuthentication.RedirectToLoginPage();
    }

    // renew ticket if needed
    var newTicket = ticket;
    if (FormsAuthentication.SlidingExpiration)
    {
        newTicket = FormsAuthentication.RenewTicketIfOld(ticket);
    }

    // set the user so that .IsAuthenticated becomes true
    // then the existing checks for user should work
    HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(newTicket), newTicket.UserData.Split(','));

}

Я не очень доволен этим как исправлением - это похоже на ужасный взлом и переизобретение колеса, но похоже, что это единственный способ для моих формстраницы с проверкой подлинности и служба REST с проверкой подлинности HTTP для работы в одном приложении.

2 голосов
/ 18 января 2011

Мне удалось заставить это работать в предыдущем проекте, но для этого требовалось использовать HTTP-модуль для выполнения пользовательской базовой аутентификации, поскольку проверка учетной записи выполняется в базе данных, а не в Windows.

Я установилвыполнить тестирование, как вы указали, с помощью одного веб-приложения в корне тестового веб-сайта и папки, содержащей службу REST.Конфигурация для корневого приложения была настроена так, чтобы запретить любой доступ:

<authentication mode="Forms">
  <forms loginUrl="Login.aspx" timeout="2880" />
</authentication>
<authorization>
  <deny users="?"/>
</authorization>

Затем мне пришлось создать приложение для папки REST в IIS и поместить файл web.config в папку REST.В этой конфигурации я указал следующее:

<authentication mode="None"/>
<authorization>
  <deny users="?"/>
</authorization>

Мне также пришлось подключить модуль http в соответствующих местах в конфигурации каталога REST.Этот модуль должен перейти в каталог bin в каталоге REST.Я использовал пользовательский базовый модуль аутентификации Доминика Байера, и этот код находится здесь .Эта версия более специфична для IIS 6, однако есть версия для IIS 7 и на codeplex , но я ее не тестировал ( предупреждение: версия IIS6 не имеетто же имя сборки и пространство имен, что и в версии IIS7.) Мне очень нравится этот базовый модуль аутентификации, поскольку он подключается прямо к модели членства ASP.NET.

Последний шаг состоял в том, чтобы гарантировать, что только анонимный доступ разрешен обоимкорневое приложение и приложение REST в IIS.

Для полноты картины я включил полные конфиги ниже.Тестовое приложение было просто приложением веб-формы ASP.NET, сгенерированным из VS 2010, оно использовало AspNetSqlProfileProvider для поставщика членства;Вот конфигурация:

<?xml version="1.0"?>

<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
      connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;Database=sqlmembership;"
    providerName="System.Data.SqlClient" />
  </connectionStrings>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />

    <authentication mode="Forms">
      <forms loginUrl="~/Account/Login.aspx" timeout="2880" />
    </authentication>

    <authorization>
      <deny users="?"/>
    </authorization>

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
          enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
          maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
        applicationName="/" />
      </providers>
    </membership>

    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/"/>
      </providers>
    </profile>

    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
      </providers>
    </roleManager>

  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

Каталог REST содержал пустой проект ASP.NET, сгенерированный из VS 2010, и я поместил в него один файл ASPX, однако содержимое папки REST не иметь , чтобы быть новым проектом.Простое добавление файла конфигурации после того, как в каталоге было приложение, связанное с ним, должно работать.Конфигурация для этого проекта следующая:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="customBasicAuthentication" type="Thinktecture.CustomBasicAuthentication.CustomBasicAuthenticationSection, Thinktecture.CustomBasicAuthenticationModule"/>
  </configSections>
  <customBasicAuthentication
    enabled="true"
    realm="testdomain"
    providerName="AspNetSqlMembershipProvider"
    cachingEnabled="true"
    cachingDuration="15"
  requireSSL="false" />

  <system.web>
    <authentication mode="None"/>
    <authorization>
      <deny users="?"/>
    </authorization>

    <compilation debug="true" targetFramework="4.0" />
    <httpModules>
      <add name="CustomBasicAuthentication" type="Thinktecture.CustomBasicAuthentication.CustomBasicAuthenticationModule, Thinktecture.CustomBasicAuthenticationModule"/>
    </httpModules>
  </system.web>
</configuration>

Надеюсь, это удовлетворит ваши потребности.

1 голос
/ 18 января 2011

Посмотрев ваши комментарии к моему предыдущему ответу, я подумал, можно ли сделать так, чтобы ваше веб-приложение автоматизировало развертывание приложения в каталоге REST.Это позволило бы вам воспользоваться преимуществами второго приложения, а также уменьшило бы нагрузку на развертывание системных администраторов.

Я думал, что вы могли бы поместить подпрограмму в глобальный метод Application_Start.asax, который проверит, что каталог REST существует, и что с ним еще не связано приложение.Если тест возвращает true, тогда происходит процесс привязки нового приложения к каталогу REST.

Еще одна мысль, которая у меня возникла, заключается в том, что вы можете использовать WIX (или другую технологию развертывания) для сборкиустановочный пакет, который ваши администраторы могли бы запустить для создания приложения, однако я не думаю, что это так автоматически, как приложение настраивает свою зависимость.

Ниже я включил пример реализации, которая проверяет IIS на наличиеданный каталог и применяет к нему приложение, если у него его еще нет.Код был протестирован с IIS 7, но также должен работать на IIS 6.

//This is part of global.asax.cs
//This approach may require additional user privileges to query IIS

//using System.DirectoryServices;
//using System.Runtime.InteropServices;

protected void Application_Start(object sender, EventArgs evt)
{
  const string iisRootUri = "IIS://localhost/W3SVC/1/Root";
  const string restPhysicalPath = @"C:\inetpub\wwwroot\Rest";
  const string restVirtualPath = "Rest";

  if (!Directory.Exists(restPhysicalPath))
  {
    // there is no rest path, so do nothing
    return;
  }

  using (var root = new DirectoryEntry(iisRootUri))
  {
    DirectoryEntries children = root.Children;

    try
    {
      using (DirectoryEntry rest = children.Find(restVirtualPath, root.SchemaClassName))
      {
        // the above call throws an exception if the vdir does not exist
        return;
      }
    }
    catch (COMException e)
    {
      // something got unlinked incorrectly, kill the vdir and application
      foreach (DirectoryEntry entry in children)
      {
        if (string.Compare(entry.Name, restVirtualPath, true) == 0)
        {
          entry.DeleteTree();
        }     
      }
    }
    catch (DirectoryNotFoundException e)
    {
      // the vdir and application do not exist, add them below
    }

    using (DirectoryEntry rest = children.Add(restVirtualPath, root.SchemaClassName))
    {
      rest.CommitChanges();
      rest.Properties["Path"].Value = restPhysicalPath;
      rest.Properties["AccessRead"].Add(true);
      rest.Properties["AccessScript"].Add(true);
      rest.Invoke("AppCreate2", true);
      rest.Properties["AppFriendlyName"].Add(restVirtualPath);
      rest.CommitChanges();
    }
  }
}

Части этого кода взяты из здесь .Удачи в вашем приложении!

1 голос
/ 17 января 2011

Возможно, это не самое элегантное решение, но я думаю, что это хорошее начало.

1) Создайте HttpModule.

2) обрабатывает событие AuthenticateRequest.

3) в обработчике событий проверьте, что запрос находится в каталоге, к которому вы хотите разрешить доступ.

4) Если это так, то вручную установите файл cookie авторизации: (или посмотрите, еслитеперь вы можете найти другой способ, когда у вас есть контроль, а аутентификация еще не произошла)

FormsAuthentication.SetAuthCookie("Anonymous", false);

5) О, почти забыл, вы хотели бы убедиться, что файл cookie авторизации был очищен, если запрос не былкаталог, к которому вы хотите предоставить доступ.

0 голосов
/ 21 февраля 2016

В .NET 4.5 теперь можно установить

Response.SuppressFormsAuthenticationRedirect = false

Проверьте эту страницу: https://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx

...