Аутентификация форм: отключить перенаправление на страницу входа в систему
У меня есть приложение, использующее ASP.NET Forms Authentication. По большей части, он отлично работает, но я пытаюсь добавить поддержку простого API через файл .ashx. Я хочу, чтобы файл ashx имел дополнительную аутентификацию (т.е. Если вы не предоставляете заголовок аутентификации, то он просто работает анонимно). Но, в зависимости от того, что вы делаете, я хочу требовать аутентификации при определенных условиях.
Я думал, что было бы просто ответить на код состояния 401, если требуемая аутентификация не была предоставлена, но похоже, что модуль Autodesk Forms перехватывает это и отвечает вместо этого перенаправлением на страницу входа. Я имею в виду, если мой метод ProcessRequest
выглядит следующим образом:
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
}
Затем вместо получения кода ошибки 401 на клиенте, как и я ожидаю, я получаю перенаправление 302 на страницу входа.
Для nornal HTTP-трафика я вижу, как это было бы полезно, но для моей страницы API я хочу, чтобы 401 проходил без изменений, чтобы клиентский клиент мог ответить на него программным способом.
Есть ли способ сделать это?
Ответы
Ответ 1
ASP.NET 4.5 добавил свойство Boolean HttpResponse.SuppressFormsAuthenticationRedirect
.
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.SuppressFormsAuthenticationRedirect = true;
}
Ответ 2
После небольшого исследования похоже, что FormsAuthenticationModule добавляет обработчик для события HttpApplicationContext.EndRequest
. В этом обработчике он проверяет код состояния 401 и в основном выполняет Response.Redirect(loginUrl)
. Насколько я могу судить, нет способа отменить это поведение, если вы хотите использовать FormsAuthenticationModule
.
Как я обошел это, отключив FormsAuthenticationModule
в файле web.config так:
<authentication mode="None" />
И затем реализуя Application_AuthenticateEvent
себя:
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Context.User == null)
{
var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);
if (oldTicket != null && !oldTicket.Expired)
{
var ticket = oldTicket;
if (FormsAuthentication.SlidingExpiration)
{
ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
if (ticket == null)
return;
}
Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
if (ticket != oldTicket)
{
// update the cookie since we've refreshed the ticket
string cookieValue = FormsAuthentication.Encrypt(ticket);
var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??
new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };
if (ticket.IsPersistent)
cookie.Expires = ticket.Expiration;
cookie.Value = cookieValue;
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.HttpOnly = true;
if (FormsAuthentication.CookieDomain != null)
cookie.Domain = FormsAuthentication.CookieDomain;
Context.Response.Cookies.Remove(cookie.Name);
Context.Response.Cookies.Add(cookie);
}
}
}
}
private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
FormsAuthenticationTicket ticket = null;
string encryptedTicket = null;
var cookie = context.Request.Cookies[name];
if (cookie != null)
{
encryptedTicket = cookie.Value;
}
if (!string.IsNullOrEmpty(encryptedTicket))
{
try
{
ticket = FormsAuthentication.Decrypt(encryptedTicket);
}
catch
{
context.Request.Cookies.Remove(name);
}
if (ticket != null && !ticket.Expired)
{
return ticket;
}
// if the ticket is expired then remove it
context.Request.Cookies.Remove(name);
return null;
}
}
На самом деле это немного сложнее, но я в основном получил код, посмотрев на реализацию FormsAuthenticationModule
в отражателе. Моя реализация отличается от встроенной FormsAuthenticationModule
тем, что она ничего не делает, если вы отвечаете 401 - не переадресовываете на страницу входа вообще. Я думаю, если это когда-нибудь станет требованием, я могу поместить элемент в контекст, чтобы отключить авто-перенаправление или что-то еще.
Ответ 3
Я не уверен, что это будет работать для всех, но в IIS7 вы можете вызвать Response.End() после того, как вы установили код состояния и описание. Таким образом, # & $^ # @*! FormsAuthenticationModule не будет перенаправлять.
public void ProcessRequest(HttpContext context) {
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.End();
}
Ответ 4
Чтобы немного поработать над zacharydl, я использовал это, чтобы решить свои проблемы. По каждому запросу, вначале, если он AJAX, немедленно подавляет поведение.
protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
Ответ 5
Я не знаю, как этот Response.End() работал на вас. Я попробовал это без радости, а затем посмотрел на MSDN для Response.End(): "останавливает выполнение страницы и вызывает событие EndRequest".
Для чего это стоило моего взлома:
_response.StatusCode = 401;
_context.Items["401Override"] = true;
_response.End();
Затем в Global.cs добавьте обработчик EndRequest (который вызывается после HTTP-модуля проверки подлинности):
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Items["401Override"] != null)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
}
}
Ответ 6
Я знаю, что уже есть ответ с тиком, но при попытке решить подобную проблему я нашел this (http://blog.inedo.com/2010/10/12/http-418-im-a-teapot-finally-a-%e2%80%9clegitimate%e2%80%9d-use/).
В основном вы возвращаете свой собственный код статуса HTTP (например, 418) в свой код. В моем случае служба данных WCF.
throw new DataServiceException(418, "401 Unauthorized");
Затем используйте HTTP-модуль для обработки его в событии EndRequest
, чтобы переписать код на 401.
HttpApplication app = (HttpApplication)sender;
if (app.Context.Response.StatusCode == 418)
{
app.Context.Response.StatusCode = 401;
}
Браузер/клиент получит правильный код контента и статуса, он отлично работает для меня:)
Если вам интересно узнать больше о статусе кода статуса 418, см. этот вопрос и ответ.
Ответ 7
то, что вы выяснили, верно в отношении форм auth, перехватывающих 401 и выполняющих перенаправление, но мы также можем сделать это, чтобы отменить это.
В основном вам нужен модуль http, чтобы перехватить перенаправление 302 на страницу входа и отменить его до 401.
Эта процедура объясняется в здесь
Данная ссылка относится к службе WCF, но она одинакова во всех сценариях сценариев формы.
Как объясняется в приведенной выше ссылке, вам также нужно очистить заголовки http, но не забудьте вернуть заголовок cookie обратно в ответ, если исходный ответ (т.е. перед перехватом) содержит файлы cookie.
Ответ 8
Это известная проблема, и там пакет NuGet для этого и/или исходный код.
Ответ 9
Вы не устанавливаете заголовок WWW-Authenticate
в отображаемом вами коде, поэтому клиент не может выполнять проверку подлинности HTTP вместо проверки подлинности форм. Если это так, вы должны использовать 403 вместо 401, который не будет перехвачен FormsAuthenticaitonModule
.
Ответ 10
Забавный хак, если вы используете .NET Framework> = v4.0, но & lt; v4.5. Он использует отражение, чтобы установить значение недоступного свойства SuppressFormsAuthenticationRedirect
:
// Set property to "true" using reflection
Response
.GetType()
.GetProperty("SuppressFormsAuthenticationRedirect")
.SetValue(Response, true, null);
Ответ 11
У меня была проблема, что я хотел избежать не только перенаправления, но и самой аутентификации форм, чтобы заставить работать веб-API. Записи в web.config с тегом местоположения для API не помогли.
Таким образом, я использовал SuppressFormAuthenticationRedirect и HttpContext.Current.SkipAuthorization для подавления аутентификации в целом.
Для идентификации отправителя я использовал, например, UserAgent в заголовке, но, конечно, рекомендуется реализовать дальнейшие шаги аутентификации, например, проверьте по отправляющему IP или отправьте другой ключ с запросом.
Ниже вставлено в Global.asax.cs.
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.UserAgent == "SECRET-AGENT")
{
AppLog.Log("Redirect suppressed");
HttpApplication context = (HttpApplication)sender;
context.Response.SuppressFormsAuthenticationRedirect = true;
HttpContext.Current.SkipAuthorization = true;
}
}
Ответ 12
Просмотрите файл Web.config в configuration\authentication
.
Если существует субэлемент forms
с атрибутом loginUrl
, удалите его и повторите попытку.