2010-02-04 3 views
15

Così stavo leggendo un'altra domanda per quanto riguarda ciclo login quando si dispone di un utente login, impostare per tornare a un URL che essi potrebbero non avere accesso a dopo il login (es. Una pagina di amministrazione, e l'utente si collega con una normale account).ASP.Net MVC come determinare se un utente può accedere a un URL?

La soluzione sotto WebForms sembra essere quello di utilizzare il metodo UrlAuthorizationModule.CheckUrlAccessForPrincipal. Tuttavia, ciò non funziona per gli URL che passano ai Metodi di azione protetti con l'Attributo Autorizza. Ho pensato che avrei potuto capire quale metodo l'URL stava puntando e riflettere su di esso per risolvere il mio problema - ma non riesco a capire come ottengo queste informazioni dalla tabella di routing.

Chiunque abbia mai lavorato con questo, o di avere una soluzione per questo? Se posso semplicemente ottenere le informazioni sul percorso da un URL, penso che potrei lavorare per il resto, ma se qualcuno ha una soluzione generica, ad es. un metodo nascosto simile a quello prima menzionato per MVC, quindi sarebbe assolutamente fantastico.

Non sto chiedendo come verificare se l'utente ha accesso a una coppia di controller/azioni specificata. Prima di tutto ho bisogno di capire come ottenere la coppia Controller/Azione da RouteTable in base all'URL. La ragione di tutta la storia di fondo è nel caso in cui esista effettivamente un equivalente a UrlAuthorizationModule.CheckUrlAccessForPrincipal per MVC.

risposta

1

Qual è il problema che si sta tentando di risolvere? Sembra che tu possa essere diretto verso una soluzione complessa che potrebbe invece utilizzare una soluzione semplice.

Se un utente non dispone delle autorizzazioni per accedere alla pagina dopo l'accesso, si desidera che gli utenti non registrati entrino in una pagina, mentre gli utenti connessi accedono a una pagina diversa?

In questo caso, potrei essere tentato di creare un altro controller solo per tali scenari e reindirizzare a quel controller ovunque l'utente non abbia accesso. O se stai usando il tuo controller di base, inserirò la funzionalità lì.

Poi il controllore potrebbe presentare la vista desiderata. Ad esempio, se un utente che non ha effettuato l'accesso tenta di accedere a una pagina, potrebbe essere reindirizzato a una pagina di errore generica. Se l'utente ha effettuato l'accesso, potrebbe essere reindirizzato a una pagina non autorizzata.

Questo è molto simile alla risposta di Robert.

Ecco uno scheletro di base per un controller di base.

public BaseController: Controller 
{ 

... // Some code 

    public ActionResult DisplayErrorPage() 
    { 
     // Assumes you have a User object with a IsLoggedIn property 
     if (User.IsLoggedIn())  
      return View("NotAuthorized"); 

     // Redirect user to login page 
     return RedirectToAction("Logon", "Account"); 
    } 

} 

Poi in consente di dire un AdminController (che eredita da BaseController) azione

public ActionResult HighlyRestrictedAction() 
{ 
    // Assumes there is a User object with a HasAccess property 
    if (User.HasAccess("HighlyRestrictedAction") == false) 
     return DisplayErrorPage(); 

    // At this point the user is logged in and has permissions 
    ... 
} 
+0

Lo accetterò per ora in quanto sembra essere la migliore soluzione per ora. Non sono sicuro che lo userò davvero.Mi piace molto l'idea di non dover inserire questo tipo di logica nei controller, inoltre vorrei evitare il reindirizzamento extra. – kastermester

1

Questo è probabilmente andando a suonare controversa, ma mi controllo di sicurezza all'inizio di ogni metodo di controllo, all'interno del metodo:

public class ProductController : Controller 
{ 
    IProductRepository _repository 

    public ActionResult Details(int id) 
    { 
     if(!_repository.UserHasAccess(id)) 
      return View("NotAuthorized"); 

     var item = _repository.GetProduct(id); 

     if (item == null) 
      return View("NotFound"); 

     return View(item); 
    } 
} 

Il motivo per cui non uso gli attributi [Authorize] di questo è che si non può passare un id, o qualsiasi altra informazione identificativa, all'attributo in fase di esecuzione.

+0

Grazie, ma non sono sicuro di come questo approccio potrebbe aiutarmi - vedere i commenti che ho lasciato per jfar. – kastermester

5

ho portato e fatto questo codice dal MvcSitemap:

public static class SecurityTrimmingExtensions 
{ 

    /// <summary> 
    /// Returns true if a specific controller action exists and 
    /// the user has the ability to access it. 
    /// </summary> 
    /// <param name="htmlHelper"></param> 
    /// <param name="actionName"></param> 
    /// <param name="controllerName"></param> 
    /// <returns></returns> 
    public static bool HasActionPermission(this HtmlHelper htmlHelper, string actionName, string controllerName) 
    { 
     //if the controller name is empty the ASP.NET convention is: 
     //"we are linking to a different controller 
     ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) 
               ? htmlHelper.ViewContext.Controller 
               : GetControllerByName(htmlHelper, controllerName); 

     var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo); 

     var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType()); 

     var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName); 

     return ActionIsAuthorized(controllerContext, actionDescriptor); 
    } 


    private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     if (actionDescriptor == null) 
      return false; // action does not exist so say yes - should we authorise this?! 

     AuthorizationContext authContext = new AuthorizationContext(controllerContext); 

     // run each auth filter until on fails 
     // performance could be improved by some caching 
     foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters) 
     { 
      authFilter.OnAuthorization(authContext); 

      if (authContext.Result != null) 
       return false; 
     } 

     return true; 
    } 

    private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName) 
    { 
     // Instantiate the controller and call Execute 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 

     IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName); 

     if (controller == null) 
     { 
      throw new InvalidOperationException(

       String.Format(
        CultureInfo.CurrentUICulture, 
        "Controller factory {0} controller {1} returned null", 
        factory.GetType(), 
        controllerName)); 

     } 

     return (ControllerBase)controller; 
    } 

Si potrebbe usare un po 'di caching, ma per il mio caso, che è stata un'ottimizzazione prematura.

+0

Il mio problema è che ho un URL, non un controller e un'azione. Il codice che hai scritto qui è il materiale che credo di poter cucinare da solo - anche se grazie per averlo condiviso, probabilmente avrò bisogno di qualcosa del genere. Ma la domanda è: come faccio a estrarre Controller + Azione dalla RouteTable da un URL? Il motivo per cui ho scritto un sacco di altre cose era perché sospettavo che potesse esserci effettivamente un metodo da chiamare per dirmi tutto questo in 1 volta. – kastermester

+0

@kastermester: per fare ciò, dovrai intercettare la richiesta prima che il metodo del controllore venga eseguito. Per le idee su come farlo, guarda qui: http://stackoverflow.com/questions/2122459/how-malleable-are-the-conventions-in-asp-net-mvc/2122521#2122521 –

+0

Proprio quello di cui avevo bisogno. Ho cercato in alto e in basso per questo. Molte grazie! –

0

Perché non attribuiscono i vostri metodi del controller con il requisito di sicurezza.

ho scritto un attributo di fare questo nel modo seguente:

public class RequiresRoleAttribute : ActionFilterAttribute 
     { 
      public string Role { get; set; } 

      public override void OnActionExecuting(ActionExecutingContext filterContext) 
      { 
       if (string.IsNullOrEmpty(Role)) 
       { 
        throw new InvalidOperationException("No role specified."); 
       } 


       if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
       { 
        filterContext.HttpContext.Response.Redirect(loginUrl, true); 
       } 
       else 
       { 
        bool isAuthorised = filterContext.HttpContext.User.IsInRole(this.Role); 

        << Complete Logic Here >> 



       } 
      }  
     } 
+0

Il mio problema è questo: un utente accede alla mia pagina di accesso. All'ingresso l'url "return" viene impostato e trasportato nella stringa di query. Supponiamo per un secondo che in qualche modo quell'URL indichi una parte amministrativa del sito. L'utente effettua l'accesso con un account normale. Ora usando questo tipo di codice (che io sono) l'utente verrebbe reindirizzato alla pagina di accesso - ma ha già effettuato l'accesso! Potrei semplicemente reindirizzare alla pagina predefinita, ma poi tutti ci arriverebbero, anche persone non autenticate. Mi piacerebbe molto verificare questo prima di reindirizzare dopo il login. – kastermester

1

Nella mia domanda ho creato un filtro personalizzato derivato da AuthorizeAttribute, in modo che qualsiasi accesso non autorizzato sarà sufficiente accedere alla pagina AccessDenied . Per i collegamenti, sostituisco Html.ActionLink con un helper personalizzato Html.SecureLink. In questa estensione helper, controllo i ruoli di questo utente per l'accesso al controller/azione rispetto al database. Se lui/lei avere l'autorizzazione, collegamento di ritorno altrimenti restituisce il testo del link con indicazioni speciali (potrebbe essere immagine/coloranti/js) risposta

8

di jfar sopra aggiornato per MVC 4:

public static class SecurityCheck 
{ 
    public static bool ActionIsAuthorized(string actionName, string controllerName) 
    { 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 
     ControllerBase controller = factory.CreateController(HttpContext.Current.Request.RequestContext, controllerName) as ControllerBase; 
     var controllerContext = new ControllerContext(HttpContext.Current.Request.RequestContext, controller); 
     var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType()); 
     var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName); 
     AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor); 
     foreach (var authAttribute in actionDescriptor.GetFilterAttributes(true).Where(a => a is AuthorizeAttribute).Select(a => a as AuthorizeAttribute)) 
     { 
      authAttribute.OnAuthorization(authContext); 
      if (authContext.Result != null) 
       return false; 
     } 
     return true; 
    } 
} 
+0

Oppure "foreach (var authAttribute in actionDescriptor.GetFilterAttributes (true) .OfType ())" - con scuse per la pedanteria. –

0

ho appena passato un po ' tempo implementando la soluzione di jfar (aggiornandola per la versione GetFilters() non deprecata), e poi ho capito che posso saltare tutta questa faccenda.

nel mio caso (e presumo la maggior parte dei casi) Ho un AuthorizationAttribution personalizzato per implementare l'autorizzazione del sito che a sua volta chiama il mio servizio di autorizzazione per effettuare la determinazione del livello di accesso effettivo.

Così nel mio html aiuto per la generazione di link del menu, ho saltato diritto al servizio di autenticazione:

@Html.MenuItem(@Url, "icon-whatever", "TargetController", "TargetAction") 

public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, UrlHelper url,string iconCss, string targetController, string targetAction) 
{ 
    var auth = IoC.Resolve<IClientAuthorizationService>().Authorize(targetController, targetAction); 
    if (auth == AccessLevel.None) 
     return MvcHtmlString.Create(""); 

* utente è determinato entro il servizio client di autenticazione

public string GetUser() { 
    return HttpContext.Current.User.Identity.Name; 
} 

* potrebbe anche aggiungere un po ' comportamento per l'accesso in sola lettura. è bello perché il mio servizio di autenticazione si occupa del caching quindi non devo preoccuparmi delle prestazioni.