Выключение 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