Выключение ASP.Net аутентификация WebForms для одного подкаталога

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

аутентификация WebForms настроена в интернете.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.

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

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

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

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

я настраиваю это на уровне приложения, хотя-это в корневой сети.config-и эта ошибка для web.конфигурации в подкаталогах.

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

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

обновление

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

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

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

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

7 ответов


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

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

в качестве альтернативы вы можете добавить веб -.настройте свою папку rest и просто сделайте следующее:

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

Регистрация этой один.


Я работал вокруг этого грязным способом-путем подделки аутентификации форм в глобальном.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(','));

}

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


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

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

мадам страница загрузки:http://www.raboof.com/projects/madam/

в моем случае остальные вызовы выполняются контроллерам (это приложение на основе MVC) в " API" область. Мадам дискриминатор устанавливается со следующей конфигурацией:

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

тогда все, что вам нужно сделать, это добавить модуль MADAM в свой интернет.config

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

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

С этой настройкой любые запросы, сделанные к URL-адресам, начиная с " API/", получат ответ 401 вместо 301, произведенного аутентификацией форм.


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

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

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

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

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

мне также пришлось подключить http-модуль в соответствующих местах в конфигурации каталога REST. Этот модуль должны перейдите в каталог bin под каталогом REST. Я использовал пользовательский базовый модуль аутентификации Доминика Байера, и этот код находится здесь. Эта версия более специфична для IIS 6, однако есть версия для IIS 7, а также на страницы CodePlex, но я не проверял это (предупреждение: версия IIS6 не имеет того же имени сборки и пространства имен, что и версия IIS7.) Мне очень нравится этот базовый модуль auth, так как он подключается прямо к 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 не есть новый проект. Просто зашел в файл config после каталогов было приложение, связанное с ним, должно работать. Конфигурация для этого проекта следующая:

<?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) Создайте HttpModule.

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

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

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

FormsAuthentication.SetAuthCookie("Anonymous", false);

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


после просмотра ваших комментариев к моему предыдущему ответу я задался вопросом, Можно ли автоматизировать развертывание приложения в каталоге 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();
    }
  }
}

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


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

Response.SuppressFormsAuthenticationRedirect = false

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