Perché AuthorizeAttribute reindirizza alla pagina di accesso per errori di autenticazione e autorizzazione?

StackOverflow https://stackoverflow.com/questions/238437

Domanda

In ASP.NET MVC, è possibile contrassegnare un metodo controller con AuthorizeAttribute , in questo modo:

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

Ciò significa che, se l'utente attualmente connesso non si trova in " CanDeleteTags " ruolo, il metodo controller non verrà mai chiamato.

Sfortunatamente, per errori, AuthorizeAttribute restituisce HttpUnauthorizedResult , che restituisce sempre il codice di stato HTTP 401. Ciò provoca un reindirizzamento alla pagina di accesso.

Se l'utente non ha effettuato l'accesso, ha perfettamente senso. Tuttavia, se l'utente è già registrato ma non ricopre il ruolo richiesto, è confuso rinviarlo alla pagina di accesso.

Sembra che AuthorizeAttribute combini autenticazione e autorizzazione.

Sembra un po 'una svista in ASP.NET MVC o mi sto perdendo qualcosa?

Ho dovuto preparare un DemandRoleAttribute che separa i due. Quando l'utente non è autenticato, restituisce HTTP 401, inviandolo alla pagina di accesso. Quando l'utente ha effettuato l'accesso, ma non ha il ruolo richiesto, crea invece un NotAuthorizedResult . Attualmente questo reindirizza a una pagina di errore.

Sicuramente non dovevo farlo?

È stato utile?

Soluzione

Quando è stato sviluppato per la prima volta, System.Web.Mvc.AuthorizeAttribute stava facendo la cosa giusta - le revisioni precedenti della specifica HTTP utilizzavano il codice di stato 401 sia per "non autorizzato" che per " e "non autenticato".

Dalla specifica originale:

  

Se la richiesta includeva già le credenziali di autorizzazione, la risposta 401 indica che l'autorizzazione è stata rifiutata per tali credenziali.

In effetti, puoi vedere la confusione proprio lì - usa la parola "autorizzazione" quando significa "autenticazione". Nella pratica quotidiana, tuttavia, ha più senso restituire un 403 proibito quando l'utente è autenticato ma non autorizzato. È improbabile che l'utente disponga di una seconda serie di credenziali che gli darebbe accesso: un'esperienza utente negativa in tutto.

Prendi in considerazione la maggior parte dei sistemi operativi: quando tenti di leggere un file non sei autorizzato ad accedere, non ti viene mostrata una schermata di accesso!

Per fortuna, le specifiche HTTP sono state aggiornate (giugno 2014) per rimuovere l'ambiguità.

Da " Hyper Text Transport Protocol (HTTP / 1.1): Autenticazione " (RFC 7235):

  

Il codice di stato 401 (non autorizzato) indica che la richiesta non è stata applicata perché manca di credenziali di autenticazione valide per la risorsa di destinazione.

Da " Hypertext Transfer Protocol (HTTP / 1.1): Semantica e contenuto " (RFC 7231):

  

Il codice di stato 403 (proibito) indica che il server ha compreso la richiesta ma si rifiuta di autorizzarla.

È interessante notare che al momento del rilascio di ASP.NET MVC 1 il comportamento di AuthorizeAttribute era corretto. Ora, il comportamento non è corretto: la specifica HTTP / 1.1 è stata corretta.

Anziché tentare di modificare i reindirizzamenti della pagina di accesso di ASP.NET, è più semplice risolvere il problema all'origine. Puoi creare un nuovo attributo con lo stesso nome ( AuthorizeAttribute ) nello spazio dei nomi predefinito del tuo sito web (questo è molto importante), quindi il compilatore lo prenderà automaticamente invece dello standard MVC uno. Naturalmente, potresti sempre dare un nuovo nome all'attributo se preferisci adottare questo approccio.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

Altri suggerimenti

Aggiungi questo alla tua funzione Page_Load di Login:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

Quando l'utente viene reindirizzato lì ma è già connesso, mostra la pagina non autorizzata. Se non hanno effettuato l'accesso, viene visualizzato e mostra la pagina di accesso.

Ho sempre pensato che avesse senso. Se hai effettuato l'accesso e cerchi di accedere a una pagina che richiede un ruolo che non ricopri, verrai reindirizzato alla schermata di accesso che ti chiede di accedere con un utente che ha il ruolo.

È possibile aggiungere una logica alla pagina di accesso che controlla se l'utente è già autenticato. Potresti aggiungere un messaggio amichevole che spiega perché sono stati confusi di nuovo lì.

Sfortunatamente, hai a che fare con il comportamento predefinito dell'autenticazione dei moduli ASP.NET. C'è una soluzione alternativa (non l'ho provato) discussa qui:

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(Non è specifico per MVC)

Penso che nella maggior parte dei casi la soluzione migliore sia limitare l'accesso a risorse non autorizzate prima che l'utente cerchi di arrivarci. Rimuovendo / disattivando il collegamento o il pulsante che potrebbe portarli a questa pagina non autorizzata.

Probabilmente sarebbe bello avere un parametro aggiuntivo sull'attributo per specificare dove reindirizzare un utente non autorizzato. Ma nel frattempo, considero il AuthorizeAttribute come una rete di sicurezza.

Prova questo nel tuo gestore Application_EndRequest del tuo file Global.ascx

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
    HttpContext.Current.Response.ClearContent();
    Response.Redirect("~/AccessDenied.aspx");
}

Se stai usando aspnetcore 2.0, usa questo:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Core
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }
}

Nel mio caso il problema era "la specifica HTTP utilizzava il codice di stato 401 per entrambi" non autorizzati " e "non autenticato" e ";" Come ha detto ShadowChaser.

Questa soluzione funziona per me:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top