AJAX y FormsAuthentication, ¿cómo evitar que FormsAuthentication anule HTTP 401?

En una aplicación configurada con FormsAuthentication, cuando un usuario accede sin la cookie de autenticación o desactualizada a una página protegida, ASP.NET emite un HTTP 401 No Autorizado, luego el módulo FormsAuthentication intercepta esta respuesta antes de que finalice la solicitud y la modifique Se encontró un HTTP 302, configurando un encabezado HTTP “Ubicación: / ruta / loginurl” para redirigir al agente de usuario a la página de inicio de sesión, luego el navegador va a esa página y recupera la página de inicio de sesión, que no está protegida, obteniendo un HTTP 200 ok

Esa fue una muy buena idea, de hecho, cuando AJAX no estaba siendo considerado.

Ahora tengo una url en mi aplicación que devuelve datos JSON y necesita que el usuario esté autenticado. Todo funciona bien, el problema es que si la cookie de autenticación caduca, cuando el código del lado de mi cliente llame al servidor, obtendrá un HTTP 200 OK con el html de la página de inicio de sesión, en lugar de un HTTP 401 No autorizado (porque se explicó anteriormente). Entonces mi parte del cliente está intentando analizar el código de inicio de sesión html como json, y está fallando.

La pregunta entonces es: ¿Cómo hacer frente a una autenticación caducada desde el lado del cliente? ¿Cuál es la solución más elegante para hacer frente a esta situación? Necesito saber cuándo la llamada ha sido exitosa o no, y me gustaría hacerlo utilizando la semántica HTTP.

¿Es posible leer encabezados HTTP personalizados desde el lado del cliente de forma segura en todos los navegadores? ¿Hay alguna manera de decirle a FormsAuthenticationModule que no realice redirecciones si la solicitud es una solicitud AJAX? ¿Hay alguna manera de anular el estado HTTP utilizando un encabezado HTTP de la misma manera que puede anular el método de solicitud HTTP?

Necesito la autenticación de formularios, y me gustaría evitar volver a escribir ese módulo o escribir mi propio módulo de autenticación de formularios.

Saludos.

Tuve el mismo problema, y ​​tuve que usar un atributo personalizado en MVC. Puede adaptarlo fácilmente para trabajar en formularios web, podría anular la autorización de sus páginas en la página base si todas sus páginas se heredan de alguna página base (el atributo global en MVC permite lo mismo: anular el método OnAuthorization para todos los controladores / acciones en solicitud)

Así es como se ve el atributo:

public class AjaxAuthorizationAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsAjaxRequest() && !filterContext.HttpContext.User.Identity.IsAuthenticated && (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0 || filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0)) { filterContext.HttpContext.SkipAuthorization = true; filterContext.HttpContext.Response.Clear(); filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; filterContext.Result = new HttpUnauthorizedResult("Unauthorized"); filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext); filterContext.HttpContext.Response.End(); } } } 

Tenga en cuenta que debe llamar a HttpContext.Response.End (); o su solicitud será redirigida al inicio de sesión (debido a esto, perdí parte de mi cabello).

En el lado del cliente, usé el método jQuery ajaxError:

 var lastAjaxCall = { settings: null, jqXHR: null }; var loginUrl = "yourloginurl"; //... //... $(document).ready(function(){ $(document).ajaxError(function (event, jqxhr, settings) { if (jqxhr.status == 401) { if (loginUrl) { $("body").prepend("
"); $("div.loginoverlay").show(); lastAjaxCall.jqXHR = jqxhr; lastAjaxCall.settings = settings; } } } }

Esto mostró el inicio de sesión en iframe sobre la página actual (parece que el usuario fue redirigido pero puede hacerlo diferente), y cuando el inicio de sesión fue exitoso, esta ventana emergente se cerró y la solicitud original de ajax se reenvió:

 if (lastAjaxCall.settings) { $.ajax(lastAjaxCall.settings); lastAjaxCall.settings = null; } 

Esto permite a sus usuarios iniciar sesión cuando la sesión expira sin perder ninguno de sus trabajos o datos escritos en el último formulario mostrado.

Estoy robando esta respuesta en gran medida de otras publicaciones, pero una idea podría ser implementar un HttpModule para interceptar el redireccionamiento a la página de inicio de sesión (instrucciones en ese enlace).

También puede modificar ese ejemplo HttpModule para que solo intercepte la redirección si la solicitud se realizó a través de AJAX si el comportamiento predeterminado es correcto cuando la solicitud no se realiza a través de AJAX:

Detectar llamada ajax, ASP.net

Así que algo a lo largo de las líneas de:

 class AuthRedirectHandler : IHttpModule { #region IHttpModule Members public void Dispose() { } public void Init(HttpApplication context) { context.EndRequest+= new EventHandler(context_EndRequest); } void context_EndRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; if (app.Response.StatusCode == 302 && app.Request.Headers["X-Requested-With"] == "XMLHttpRequest" && context.Response.RedirectLocation.ToUpper().Contains("LOGIN.ASPX")) { app.Response.ClearHeaders(); app.Response.ClearContent(); app.Response.StatusCode = 401; } } #endregion } 

También puede asegurarse de que la redirección sea a su página de inicio de sesión real si hay otras redirecciones 302 legítimas en su aplicación.

Entonces simplemente agregarías a tu web.config:

     

De todos modos. Una vez más, el pensamiento original real entró en esta respuesta, solo estoy juntando varios bits de SO y otras partes de la web.

Estaba teniendo problemas para implementar la respuesta aceptada . Principalmente, mis registros de errores se llenaron con Server cannot set status after HTTP headers have been sent errores.

Intenté implementar la respuesta a la pregunta aceptada. El servidor no puede establecer el estado después de que se hayan enviado los encabezados HTTP IIS7.5 , nuevamente no fue exitoso.

Buscando un poco en Google me topé con la propiedad SuppressFormsAuthenticationRedirect

Si su versión .Net es> = 4.5, entonces puede agregar el siguiente código al método HandleUnauthorizedRequest de su clase personalizada AuthorizeAttribute .

 public sealed class CustomAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsAjaxRequest()) { filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; base.HandleUnauthorizedRequest(filterContext); return; } base.HandleUnauthorizedRequest(filterContext); return; } } 

La parte importante es el bloque if . Esto es lo más sencillo de hacer si está en .Net 4.5 y ya cuenta con una autorización personalizada.