Seguridad y Autorizacion centralizada en base de datos con Asp.Net MVC

por Andres Stang  22. diciembre 2010

La palabra Seguridad es siempre una consulta que recibo cuando hablo de cómo programar en Asp.Net, y siempre me gusta dar el mismo chiste como respuesta: “Vamos por partes decía Jack el Destripador”. Es importante dividir un problema grande en problemas más pequeños y por ende más manejables, y hablando de seguridad la fórmula es sencilla.

 

SEGURIDAD = AUTENTICACIÓN + AUTORIZACIÓN


Es decir, tenes que resolver por un lado como identificar al usuario, para saber que es quien dice ser que es, y por el otro lado una vez que sepamos su identidad, tenemos que decidir si lo dejamos acceder o no.

Cuando creamos un proyecto nuevo de MVC, el template nos da una GRAN ayuda y propone resolver (mejor dicho, ya lo implementa y lo resuelve por defecto) cada tema con un componente específico. Para la autenticación utiliza Asp.Net Membership y para la autorización el decorador [Authorize].

Ambos componentes funcionan maravillosamente pero hay uno que me gusta y otro que no. Concretamente Asp.Net Membership me parece perfecto para resolver una gran cantidad de escenarios y contextos, y generalmente lo utilizo en mis proyectos. Pero el decorador de autorización a mi criterio tiene dos grandes falencias:

  • Falta de flexibilidad: el componente de autorización puede recibir como parámetro el usuario y el rol, pero esto queda explícito en el código, es decir, si me surge la necesidad de agregar un nuevo rol al sistema deberé recorrer el código, cambiarlo, recompilar y realizar un deploy del sitio.
  • Verborragico: el componente requiere gran escritura y definición por parte del programador. Esto, mas allá de la poca elegancia del código, define que sea el programador quien decide quien esté autorizado a entrar a cada acción. Y ante un error, descuido o falta de memoria del programador podemos comprometer a nuestra aplicación dejando expuesta funcionalidad sensible a usuarios indeseados.

 

¿Cómo resolvemos esto?


Allá por el año 2007 Daniel Laco escribió uno de los post más consultados de este sitio:


Autenticación de usuarios basada en Roles utilizando HTTPModules en ASP.NET


De forma muy sencilla y elegante resuelve las dos falencias presentadas. Y si bien el código no es compatible con todas las versiones del .net framework, o con el paradigma MVC, el concepto si aplica en el universo Asp.net.

Lo que haremos el día de hoy, es actualizar un poco el código para que puede ser utilizado en nuestros proyectos de MVC ;)

Lo primero que tenemos que hacer es programar el método de nuestro modulo, quedaría algo así:

 

 public void Init(HttpApplication context)
 {
     context.AuthorizeRequest += new EventHandler(OnAuthorizeRequest);
 }
 
 void OnAuthorizeRequest(object sender, EventArgs e)
 {
     HttpContext context = ((HttpApplication)sender).Context;
 
     RouteData routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
 
     if (routeData != null)
     {
         string controller = routeData.GetRequiredString("controller");
         string action = routeData.GetRequiredString("action");
 
         if (!EstaAutorizado(controller, action, context.User))
         {
             /// 401 es el codigo de resultado HTTP para acceso no autorizado
             context.Response.StatusCode = 401; 
             context.Response.End();
         }
     }
 }

 

En el código vemos cómo recuperar la información de la ruta para saber que método estamos ejecutando, y luego definir que si no se encuentra autorizado el usuario, le responderemos con un código HTTP estándar: 401 (lista de códigos)

De esta forma dejamos actuar al framework de asp.net que ya tiene resuelto el enganche y resolución del caso. Por ejemplo, MVC viene configurado por defecto que ante un error de autenticación redirija al usuario automáticamente a la página de login:

 

<authentication mode="Forms">
   <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>

 

Lo único que nos falta, es recordar agregar el modulo a nuestro web.config para que se ejecute:

<httpModules>
  <add name="MiModulo" type="AutorizacionMVCHttpModule.Models.MiModulo"/>
</httpModules>

 

Listo! Con muy pocas líneas hemos resuelto de manera elegante y limpia un requerimiento esencial y controversial en la mayoría de nuestros proyectos.

Les dejo el código fuente de ejemplo para que lo puedan descargar y ver implementado
Download AutorizacionMVCHttpModule.zip (262,77 kb)

Happy coding!

Tags: , , , , ,

.NET | ASP.NET | Desarrollo Web

WCF TIPS - Como generar una descarga http estándar y hostearla desde un servicio

por maxi  20. septiembre 2010

    Objectivo: Crear un ejemplo de servicio con WCF que permita a un navegador descargar un archivo por http en forma estándar.

    Características del servicio:

    • Self-Host: En este caso va a ser Self-host  en una aplicacion Winforms asi puede ser hosteado en cualquier maquina sin necesidad de configurar un IIS.

    • REST: No se utilizara SOA, ya que REST es más liviano dado que el mensaje no tiene toda la información del Envelope SOAP.

      Streamming: El servicio enviará el archivo en mediante Streamming lo que permite manejar archivos grandes.

    • Multithreading: el servicio no será Single es decir una instancia del servicio atenderá cada llamada, de esta forma el servicio no encolara las solicitudes si no que podrá atender varias descargas al mismo tiempo y a diferentes clientes.

      Lo primero que haremos será crear el contrato, al que llame IDownloadService que contiene el método DownloadFileHttp, el cual recibe el path y el nombre del archivo para descargar y retornara el Stream.

      Esta claro que no es ideal recibir el path del archivo en el servicio, pero lo hice para ejemplificar como recibir varios parametros ya que este servicio utiliza REST por lo cual el servicio recibe los parametros en forma de query string. El atributo WebGet precisamente nos permite definir que el servicio recibira la solicitud mediante una url de la siguiente forma:

                                       DirecciónBase           / download    / path / fileName

      P.e.: http://localhost:9000/DownloadService / download    / C:@mp3  /   tema.mp3

       
           [ServiceContract]
          public interface IDownloadService
          {
              [OperationContract]
              [WebGet(UriTemplate = "download/{path}/{fileName}")]
              Stream DownloadFileHttp(string path, string fileName);
          }
       

      Una vez definido el contrato vamos  a implementarlo de la siguiente manera:

       
        [ServiceBehavior(
              //Cada solicitud crea una nueva instancia de StreamerSvc y se recicla despeus de la llamada 
              //De esta forma las llamadas no se encolan se puede atender a varios clientes
              InstanceContextMode = InstanceContextMode.PerCall,
              //La instancia del servicio es multi threaded
              //Los threads pueden llamar a cualquier operacion del servicio en cualquier momento
              //Multiple implica que le programador tiene que manejar la sincronizacion con locks
              //Pero como el servicio no maneja estados, p.e. variables o atributo de clase no me afecta
              ConcurrencyMode = ConcurrencyMode.Multiple,
              //Esta propiedad hay que setearla en este ejemplo ya que el servicio esta siendo hosteado en una aplicacon Winforms
              //Implica que todas las llamadas al servicio correran en el mismo thread de la UI que creo el servicio (new ServiceHost(...)))
              //La pongo en false para evitar deadlocks ya que por defecto es true
              UseSynchronizationContext = false)]    public class DownloadSvc : IDownloadStream
          {
              public Stream DownloadFileHttp(string path, string fileName)
              {
      //Truco para poder poner el path en la url solo para este ejemplo
                      path = path.Replace("@", @"\");@", @"\");File does not exists!");application/download";content-disposition", string.Format("attachment;filename=\"{0}\"", fileName));
                      fileName = fileName.Replace("
                      path = Path.Combine(path, fileName);
       
                      if (!File.Exists(path))
                      {
                          throw new FaultException("
                      }
              
                      //Abrimos el archivo 
                      var fileStream = File.OpenRead(path);
       
                      //Configurar Headers para el cliente p.e. un browser
                      var headers = WebOperationContext.Current.OutgoingResponse.Headers;
                      WebOperationContext.Current.OutgoingResponse.ContentType = "
                      headers.Add("
                      headers.Add("content-length", fileStream.Length.ToString());
       
               //Retonranos FileStream
                      return fileStream;
              }}
        

      Muy bonito pero para que todo esto funcione correctamente debemos configurar nuestro servicio. Para esto vamos a setear las siguientes entradas en el config:

       
      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
               <system.serviceModel>
                         <bindings>
                                  <mexHttpBinding>
                                            <binding name="MetadataBinding" />
                                  </mexHttpBinding>
                                  <webHttpBinding>
                                            <binding name="DownloadBinding" sendTimeout="00:10:00" maxBufferPoolSize="2147483647"
                                             maxReceivedMessageSize="2147483647" transferMode="Streamed">
                                            </binding>
                                  </webHttpBinding>
                         </bindings>
                         <services>
                                  <service behaviorConfiguration="ServiceBehavior" name="StreamerSvcHost.StreamerSvc">                             
                                            <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration="MetadataBinding"
                                             name="MetadataEndpoint" contract="IMetadataExchange" />
                                            <endpoint address="FileSvc" behaviorConfiguration="RestBehavior"
                                             binding="webHttpBinding" bindingConfiguration="DownloadBinding"
                                             name="DownloadEndpoint" contract="StreamerSvcHost.IDownloadService" />
                                            <host>
                                                     <baseAddresses>
                                                              <add baseAddress="http://localhost:9000/" />
                                                     </baseAddresses>
                                            </host>
                                  </service>
                         </services>
                         <behaviors>
                                  <endpointBehaviors>
                                            <behavior name="RestBehavior">
                                                     <webHttp />
                                            </behavior>
                                  </endpointBehaviors>
                                  <serviceBehaviors>
                                            <behavior name="ServiceBehavior">
                                                     <serviceMetadata httpGetEnabled="true" />
                                                     <serviceDebug includeExceptionDetailInFaults="true" />
                                                     <dataContractSerializer maxItemsInObjectGraph="2147483647" />
                                            </behavior>
                                  </serviceBehaviors>
                         </behaviors>
               </system.serviceModel>

      </configuration>

       

        Lo destacable es lo siguiente:

        • El endpoint deberá tener un webHttpBinding, esto permite exponer el servicio para ser accedido mediante HTTP requests  en lugar de mensajes SOAP.

        • El binding se deberá configurar con TransferMode = Streamed

        • El endpoint deberá tener configura un EndpointBehavior agregándole le elemento webHttp, para que un cliente pueda comunicarse por http con el servicio este debe tener asociado este comportamiento.

        Tags: , , , , , ,

        WCF

        Acerca de los Autores

        Este es el blog del equipo de VEMN SA 
        Presentaremos temas que nos parezcan de interés sobre tecnología .NET, Procesos y Metodologías y todo aquello relacionado con el proceso de desarrollo de Software

        Month List