Autorización basada en usuario (C#)

por Scott Mitchell

Nota:

Desde que se escribió este artículo, los proveedores de pertenencia de ASP.NET han sido reemplazados por ASP.NET Identity. Se recomienda encarecidamente actualizar las aplicaciones para usar la plataforma ASP.NET Identity en lugar de los proveedores de pertenencia destacados en el momento en el que se escribió este artículo. ASP.NET Identity ofrece una serie de ventajas frente al sistema de pertenencia ASP.NET, incluidas las siguientes:

  • Mejor rendimiento
  • Extensibilidad y capacidad de prueba mejoradas
  • Compatibilidad con OAuth, OpenID Connect y autenticación en dos fases
  • Compatibilidad con identidades basadas en notificaciones
  • Mejor interoperabilidad con ASP.Net Core

Descargar código o Descargar PDF

En este tutorial veremos cómo limitar el acceso a páginas y restringir la funcionalidad a nivel de página mediante diversas técnicas.

Introducción

La mayoría de las aplicaciones web que ofrecen cuentas de usuario lo hacen en parte para restringir que determinados visitantes accedan a determinadas páginas del sitio. En la mayoría de los sitios del panel de mensajes en línea, por ejemplo, todos los usuarios (anónimos y autenticados) pueden ver las publicaciones del panel de mensajes, pero solo los usuarios autenticados pueden visitar la página web para crear una nueva publicación. Y puede haber páginas administrativas que solo sean accesibles para un usuario determinado (o un conjunto determinado de usuarios). Además, la funcionalidad de nivel de página puede diferir por usuario. Al ver una lista de publicaciones, los usuarios autenticados se muestran una interfaz para clasificar cada publicación, mientras que esta interfaz no está disponible para los visitantes anónimos.

ASP.NET facilita la definición de reglas de autorización basadas en el usuario. Con un poco de marcado en Web.config, se pueden bloquear páginas web específicas o directorios completos para que solo sean accesibles para un subconjunto especificado de usuarios. La funcionalidad de nivel de página se puede activar o desactivar en función del usuario que ha iniciado sesión actualmente a través de medios declarativos y mediante programación.

En este tutorial veremos cómo limitar el acceso a páginas y restringir la funcionalidad a nivel de página mediante diversas técnicas. Comencemos.

Un vistazo al flujo de trabajo de autorización de direcciones URL

Como se describe en el tutorial Introducción a la autenticación de formularios, cuando el tiempo de ejecución de ASP.NET procesa una solicitud de un recurso de ASP.NET, la solicitud genera una serie de eventos durante su ciclo de vida. Los Módulos HTTP son clases administradas cuyo código se ejecuta en respuesta a un evento determinado en el ciclo de vida de la solicitud. ASP.NET incluye una serie de módulos HTTP que realizan tareas esenciales en segundo plano.

Uno de estos módulos HTTP es FormsAuthenticationModule. Como se explicó en los tutoriales anteriores, la función principal de FormsAuthenticationModule es determinar la identidad de la solicitud actual. Esto se logra inspeccionando el vale de autenticación de formularios, que se encuentra en una cookie o incrustada dentro de la dirección URL. Esta identificación tiene lugar durante el AuthenticateRequestevento.

Otro módulo HTTP importante es UrlAuthorizationModule, que se genera en respuesta al AuthorizeRequestevento (que sucede después del evento AuthenticateRequest). UrlAuthorizationModuleExamina el marcado de configuración en Web.config para determinar si la identidad actual tiene autoridad para visitar la página especificada. Este proceso se conoce como Autorización de direcciones URL.

Examinaremos la sintaxis de las reglas de autorización de direcciones URL en el paso 1, pero primero veremos lo que UrlAuthorizationModule hace en función de si la solicitud está autorizada o no. Si UrlAuthorizationModule determina que la solicitud está autorizada, no hace nada y la solicitud continúa a lo largo de su ciclo de vida. Sin embargo, si la solicitud no está autorizada, el anula el ciclo de vida UrlAuthorizationModule e indica al objeto Response que devuelva un estado HTTP 401 no autorizado. Cuando se usa la autenticación de formularios, este estado HTTP 401 nunca se devuelve al cliente porque si FormsAuthenticationModule detecta un estado HTTP 401 lo modifica a un redireccionamiento HTTP 302 al página de registro.

En la figura 1 se muestra el flujo de trabajo de la canalización de ASP.NET FormsAuthenticationModule, y el UrlAuthorizationModule cuando llega una solicitud no autorizada. En concreto, la figura 1 muestra una solicitud de un visitante anónimo para ProtectedPage.aspx, que es una página que deniega el acceso a usuarios anónimos. Dado que el visitante es anónimo, anula la solicitud UrlAuthorizationModuley devuelve un estado HTTP 401 No autorizado. A continuación, FormsAuthenticationModule convierte el estado 401 en una redirección 302 a la página de registro. Una vez autenticado el usuario a través de la página de registro, se le redirigirá a ProtectedPage.aspx. Esta vez, FormsAuthenticationModule identifica al usuario en función de su vale de autenticación. Ahora que el visitante está autenticado, UrlAuthorizationModule permite el acceso a la página.

The Forms Authentication and URL Authorization Workflow

Figura 1: Flujo de trabajo de autenticación y autorización de direcciones URL de formularios (Haga clic para ver la imagen en tamaño completo)

En la figura 1 se muestra la interacción que se produce cuando un visitante anónimo intenta acceder a un recurso que no está disponible para los usuarios anónimos. En tal caso, el visitante anónimo se redirige a la página de inicio de sesión con la página que ha intentado visitar especificada en la cadena de consulta. Una vez que el usuario ha iniciado sesión correctamente, se le redirigirá automáticamente al recurso que inicialmente intentó ver.

Cuando un usuario anónimo realiza la solicitud no autorizada, este flujo de trabajo es sencillo y es fácil para que el visitante comprenda lo que ha ocurrido y por qué. Pero tenga en cuenta que FormsAuthenticationModule redirigirá a cualquier usuario no autorizado a la página de registro, incluso si la solicitud la realiza un usuario autenticado. Esto puede dar lugar a una experiencia de usuario confusa si un usuario autenticado intenta visitar una página para la que carece de autoridad.

Imagine que nuestro sitio web tenía sus reglas de autorización de dirección URL configuradas de modo que la página de ASP.NET OnlyTito.aspx solo tenía acceso a Tito. Ahora, imagine que Sam visita el sitio, inicia sesión y a continuación, intente visitar OnlyTito.aspx. El UrlAuthorizationModule detendrá el ciclo de vida de la solicitud y devolverá un estado HTTP 401 No autorizado, que el FormsAuthenticationModule detectará y redirigirá a Sam a la página de registro. Sin embargo, dado que Sam ya se ha registrado, es posible que se pregunte por qué se le ha enviado de vuelta a la página de registro. Podría razonar que sus credenciales de registro se perdieron de alguna manera, o que ha especificado credenciales no válidas. Si Sam vuelve a escribir sus credenciales desde la página de registro, se iniciará sesión (de nuevo) y se le redirigirá a OnlyTito.aspx. El UrlAuthorizationModule detectará que Sam no puede visitar esta página y se le devolverá a la página de registro.

En la figura 2 se muestra este flujo de trabajo confuso.

The Default Workflow Can Lead to a Confusing Cycle

figura 2: El flujo de trabajo predeterminado puede provocar un ciclo confuso (Haga clic para ver la imagen en tamaño completo)

El flujo de trabajo ilustrado en la Figura 2 puede confundir rápidamente incluso al visitante más experto en informática. Veremos formas de evitar este ciclo confuso en el paso 2.

Nota:

ASP.NET usa dos mecanismos para determinar si el usuario actual puede acceder a una página web determinada: autorización de direcciones URL y autorización de archivos. La autorización de archivos se implementa medianteFileAuthorizationModule, que determina la autoridad mediante una consulta a las ACL solicitadas. La autorización de archivos se usa normalmente con la autenticación de Windows porque las ACL son permisos que se aplican a las cuentas de Windows. Al usar la autenticación de formularios, todas las solicitudes de sistema operativo y de nivel de sistema de archivos se ejecutan mediante la misma cuenta de Windows, independientemente del usuario que visite el sitio. Dado que esta serie de tutoriales se centra en la autenticación de formularios, no se analizará la autorización de archivos.

Ámbito de autorización de dirección URL

El UrlAuthorizationModule es código administrado que forma parte del entorno de ejecución de ASP.NET. Antes de la versión 7 del servidor web de Internet Information Services (IIS) de Microsoft, había una barrera distinta entre la canalización HTTP de IIS y la canalización del entorno de ejecución de ASP.NET. En resumen, en IIS 6 y anteriores, el ASP.NET UrlAuthorizationModule solo se ejecuta cuando una solicitud se delega desde IIS al ejecutar de ASP.NET. De manera predeterminada, IIS procesa contenido estático, como páginas HTML y CSS, JavaScript y archivos de imagen, y solo entrega las solicitudes al entorno de ejecución de ASP.NET cuando se solicita una página con una extensión de .aspx, .asmx, o .ashx.

Sin embargo, IIS 7 permite canalizaciones integradas de IIS y ASP.NET. Con algunas opciones de configuración, puede configurar IIS 7 para invocar UrlAuthorizationModule para todas las solicitudes de , lo que significa que las reglas de autorización de direcciones URL se pueden definir para archivos de cualquier tipo. Además, IIS 7 incluye su propio motor de autorización de direcciones URL. Para obtener más información sobre la integración de ASP.NET y la funcionalidad de autorización de direcciones URL nativas de IIS 7, consulte Descripción de la autorización de direcciones URL de IIS7. Para obtener una visión más detallada de ASP.NET y la integración de IIS 7, recoge una copia del libro de Shahram Khosravi, Professional IIS 7 y ASP.NET Integrated Programming (ISBN: 978-0470152539).

En pocas palabras, en versiones anteriores a IIS 7, las reglas de autorización de direcciones URL solo se aplican a los recursos administrados por el entorno de ejecución de ASP.NET. Pero con IIS 7 es posible usar la característica de autorización de direcciones URL nativas de IIS o integrar ASP. NET UrlAuthorizationModule en la canalización HTTP de IIS, lo que amplía esta funcionalidad a todas las solicitudes.

Nota:

Existen algunas diferencias sutiles pero importantes en la forma en que ASP.NETUrlAuthorizationModule y la característica de autorización de direcciones URL de IIS 7 procesan las reglas de autorización. En este tutorial no se examina la funcionalidad de autorización de direcciones URL de IIS 7 ni las diferencias en la forma en que analiza las reglas de autorización en comparación con UrlAuthorizationModule. Para obtener más información sobre estos temas, consulte la documentación de IIS 7 en MSDN o en www.iis.net.

Paso 1: Definir reglas de autorización de direcciones URL en Web.config

UrlAuthorizationModule determina si se debe conceder o denegar el acceso a un recurso solicitado para una identidad determinada en función de las reglas de autorización de dirección URL definidas en la configuración de la aplicación. Las reglas de autorización se detallan en el <authorization> elemento en forma de elemento secundario <allow> y <deny>. Cada elemento secundario <allow> y <deny> puede especificar:

  • Un usuario determinado
  • Una lista delimitada por comas de usuarios
  • Todos los usuarios anónimos, indicados por un signo de interrogación (?)
  • Todos los usuarios, indicados por un asterisco (*)

El marcado siguiente muestra cómo usar las reglas de autorización de dirección URL para permitir a los usuarios Tito y Scott y denegar a todos los demás:

<authorization>
 <allow users="Tito, Scott" />
 <deny users="*" />
</authorization>

El elemento <allow> define qué usuarios se permiten (Tito y Scott) mientras que el elemento <deny> indica que se deniegan todos los usuarios .

Nota:

Los elementos <allow> y <deny> también pueden especificar reglas de autorización para los roles. Examinaremos la autorización basada en roles en un tutorial futuro.

La siguiente configuración concede acceso a cualquier persona que no sea Sam (incluidos los visitantes anónimos):

<authorization>
 <deny users="Sam" />
</authorization>

Para permitir solo usuarios autenticados, use la siguiente configuración, que deniega el acceso a todos los usuarios anónimos:

<authorization>
 <deny users="?" />
</authorization>

Las reglas de autorización se definen dentro del elemento <system.web> en Web.config y se aplican a todos los recursos de ASP.NET de la aplicación web. A menudo, una aplicación tiene reglas de autorización diferentes para diferentes secciones. Por ejemplo, en un sitio de comercio electrónico, todos los visitantes pueden examinar los productos, ver reseñas de productos, buscar en el catálogo, etc. Sin embargo, solo los usuarios autenticados pueden llegar a la desprotección o las páginas para administrar el historial de envíos de uno. Además, puede haber partes del sitio a las que solo pueden acceder los usuarios seleccionados, como los administradores del sitio.

ASP.NET facilita la definición de diferentes reglas de autorización para diferentes archivos y carpetas del sitio. Las reglas de autorización especificadas en el archivo Web.config de la carpeta raíz se aplican a todos los recursos ASP.NET del sitio. Sin embargo, esta configuración de autorización predeterminada puede anularse para una carpeta concreta agregando un Web.config con una sección <authorization>.

Vamos a actualizar nuestro sitio web para que solo los usuarios autenticados puedan visitar las páginas de ASP.NET de la carpeta Membership. Para ello, es necesario agregar un archivo Web.config a la carpeta Membership y establecer su configuración de autorización para denegar a los usuarios anónimos. Haga clic con el botón derecho en la carpeta Membership en el Explorador de soluciones, elija el menú Agregar nuevo elemento en el menú contextual y agregue un nuevo archivo de configuración web denominado Web.config.

Add a Web.config File to the Membership Folder

Figura 3: Agregar un archivo Web.config a la carpeta Membership (Haga clic para ver la imagen en tamaño completo)

En este punto, el proyecto debe contener dos archivosWeb.config: uno en el directorio raíz y otro en la carpetaMembership.

Your Application Should Now Contain Two Web.config Files

Figura 4: Su aplicación debe contener ahora dos archivos Web.config (Haga clic para ver la imagen en tamaño completo)

Actualice el archivo de configuración de la carpeta Membership para prohibir el acceso a usuarios anónimos.

<?xml version="1.0"?>
<configuration>
 <system.web>
 <authorization>
 <deny users="?" />
 </authorization>
 </system.web>
</configuration>

Eso es todo.

Para probar este cambio, visite la página principal en un explorador y asegúrese de que ha cerrado la sesión. Dado que el comportamiento predeterminado de una aplicación de ASP.NET es permitir a todos los visitantes, y dado que no hemos realizado ninguna modificación de autorización en el archivo Web.config del directorio raíz, podemos visitar los archivos en el directorio raíz como visitante anónimo.

Haga clic en el vínculo Crear cuentas de usuario que se encuentra en la columna izquierda. Esto le llevará al ~/Membership/CreatingUserAccounts.aspx. Dado que el archivo Web.config de la carpeta Membership define reglas de autorización para prohibir el acceso anónimo, anula la solicitud UrlAuthorizationModule y devuelve un estado HTTP 401 No autorizado. El FormsAuthenticationModule lo modifica a un estado de redirección 302, enviándonos a la página de inicio de sesión. Tenga en cuenta que la página a la que intentamos acceder (CreatingUserAccounts.aspx) se pasa a la página de inicio de sesión a través del parámetro querystring ReturnUrl.

Since the URL Authorization Rules Prohibit Anonymous Access, We are Redirected to the Login Page

Figura 5: Dado que las reglas de autorización de direcciones URL prohíben el acceso anónimo, se redirige a la página de inicio de sesión (Haga clic para ver la imagen en tamaño completo)

Después de iniciar sesión correctamente, se redirige a la página CreatingUserAccounts.aspx. Esta vez, UrlAuthorizationModule permite el acceso a la página porque ya no somos anónimos.

Aplicación de reglas de autorización de direcciones URL a una ubicación específica

La configuración de autorización definida en la sección <system.web> de Web.config se aplica a todos los recursos de ASP.NET de ese directorio y sus subdirectorios (hasta que otro archivo Web.config invalide lo contrario). Sin embargo, en algunos casos, es posible que deseemos que todos los recursos de ASP.NET de un directorio determinado tengan una configuración de autorización determinada, excepto para una o dos páginas específicas. Esto se puede lograr agregando un elemento <location> en Web.config, apuntando al archivo cuyas reglas de autorización difieren y definiendo sus reglas de autorización únicas en ella.

Para ilustrar el uso del elemento <location> para invalidar los valores de configuración de un recurso específico, vamos a personalizar la configuración de autorización para que solo Tito pueda visitar CreatingUserAccounts.aspx. Para ello, agregue un elemento <location> al archivo Web.config de la carpeta Membership y actualice su marcado para que tenga el siguiente aspecto:

<?xml version="1.0"?>
<configuration>
 <system.web>
 <authorization>
 <deny users="?" />
 </authorization>
 </system.web>

 <location path="CreatingUserAccounts.aspx">
 <system.web>
 <authorization>
 <allow users="Tito" />
 <deny users="*" />
 </authorization>
 </system.web>
 </location>
</configuration>

El elemento <authorization> en <system.web> define las reglas de autorización de direcciones URL predeterminadas para recursos ASP.NET de la carpeta Membership y sus subcarpetas. El elemento <location> nos permite invalidar estas reglas para un recurso determinado. En el marcado anterior, el elemento <location> hace referencia a la página CreatingUserAccounts.aspx y especifica sus reglas de autorización como permitir a Tito, pero denegar a todos los demás.

Para probar este cambio de autorización, comience visitando el sitio web como un usuario anónimo. Si intenta visitar cualquier página de la carpeta Membership, como UserBasedAuthorization.aspx, el UrlAuthorizationModule denegará la solicitud y se le redirigirá a la página de inicio de sesión. Después de iniciar sesión como, por ejemplo, Scott, puede visitar cualquier página de la carpeta Membershipexcepto para CreatingUserAccounts.aspx. Si intenta visitar CreatingUserAccounts.aspx ha iniciado sesión como cualquiera, pero Tito dará lugar a un intento de acceso no autorizado, lo que le redirigirá de nuevo a la página de inicio de sesión.

Nota:

El elemento <location> debe aparecer fuera del elemento<system.web> de la configuración. Debe usar un elemento <location> independiente para cada recurso cuya configuración de autorización quiera invalidar.

Vea cómo UrlAuthorizationModule usa las reglas de autorización para conceder o denegar el acceso

UrlAuthorizationModule determina si se debe autorizar una identidad determinada para una dirección URL determinada mediante el análisis de las reglas de autorización de direcciones URL una a la vez, empezando por la primera y trabajando hacia abajo. Tan pronto como se encuentre una coincidencia, se concede o deniega el acceso al usuario, en función de si la coincidencia se encontró en un elemento <allow> o <deny>. Si no se encuentra ninguna coincidencia, se concede acceso al usuario. Por lo tanto, si desea restringir el acceso a una o varias cuentas de usuario, es imperativo usar un elemento <deny> como último elemento en la configuración de autorización de dirección URL. Si omite un elemento<deny>, se concederá acceso a todos los usuarios.

Para comprender mejor el proceso utilizado por la UrlAuthorizationModule autoridad para determinar la autoridad, considere las reglas de autorización de direcciones URL de ejemplo que hemos examinado anteriormente en este paso. La primera regla es un elemento <allow> que permite el acceso a Tito y Scott. La segunda regla es un elemento <deny> que deniega el acceso a todos. Si nos visita un usuario anónimo, UrlAuthorizationModule empieza preguntando: ¿Es anónimo Scott o Tito? La respuesta, obviamente, es No, por lo que continúa con la segunda regla. ¿Está anónimo en el conjunto de todos? Dado que la respuesta aquí es Sí, la regla <deny> se aplica y el visitante se redirige a la página de inicio de sesión. Del mismo modo, si Jisun está de visita, UrlAuthorizationModule empieza preguntando: ¿Jisun es Scott o Tito? Como no es, el UrlAuthorizationModule continúa con la segunda pregunta, ¿Es Jisun en el conjunto de todos? Lo está, así que a ella también se le deniega el acceso. Por último, en caso de que Tito realice una visita, la primera pregunta planteada por UrlAuthorizationModule es una respuesta afirmativa, por lo que se concede acceso a Tito.

Dado que UrlAuthorizationModule procesa las reglas de autorización de arriba hacia abajo, deteniéndose en cualquier coincidencia, es importante que las reglas más específicas vayan antes que las menos específicas. Es decir, para definir reglas de autorización que prohíben a los usuarios Jisun y anónimos, pero permitir a todos los demás usuarios autenticados, empezaría con la regla más específica (la que afecta a Jisun) y a continuación, continuar con las reglas menos específicas, las que permiten a todos los demás usuarios autenticados, pero denegar a todos los usuarios anónimos. Las siguientes reglas de autorización de direcciones URL implementan esta directiva denegando primero Jisun y, a continuación, denegando a cualquier usuario anónimo. A cualquier usuario autenticado que no sea Jisun se le concederá acceso porque ninguna de estas <deny> instrucciones coincidirá.

<authorization>
 <deny users="Jisun" />
 <deny users="?" />
</authorization>

Paso 2: Corregir el flujo de trabajo para usuarios no autorizados y autenticados

Como se ha descrito anteriormente en este tutorial en la sección A Look at the URL Authorization Workflow (Un vistazo al flujo de trabajo de autorización de dirección URL), siempre que transcurra una solicitud no autorizada, UrlAuthorizationModule anula la solicitud y devuelve un estado HTTP 401 No autorizado. Este estado 401 lo modifica FormsAuthenticationModule en un estado de redirección 302 que envía al usuario a la página de inicio de sesión. Este flujo de trabajo se produce en cualquier solicitud no autorizada, incluso si el usuario está autenticado.

Devolver un usuario autenticado a la página de inicio de sesión es probable que los confunda, ya que ya han iniciado sesión en el sistema. Con un poco de trabajo, podemos mejorar este flujo de trabajo mediante la redirección de usuarios autenticados que realizan solicitudes no autorizadas a una página que explica que han intentado acceder a una página restringida.

Empiece por crear una nueva página de ASP.NET en la carpeta raíz de la aplicación web denominada UnauthorizedAccess.aspx; no olvide asociar esta página a la Site.master página maestra. Después de crear esta página, quite el control Content que hace referencia a LoginContent ContentPlaceHolder para que se muestre el contenido predeterminado de la página maestra. A continuación, agregue un mensaje que explique la situación, es decir, que el usuario intentó acceder a un recurso protegido. Después de agregar este mensaje, el marcado declarativo de la página UnauthorizedAccess.aspx debe tener un aspecto similar al siguiente:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeFile="UnauthorizedAccess.aspx.cs" Inherits="UnauthorizedAccess"
Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"
Runat="Server">
 <h2>Unauthorized Access</h2>
 <p>
 You have attempted to access a page that you are not authorized to view.
 </p>
 <p>
 If you have any questions, please contact the site administrator.
 </p>
</asp:Content>

Ahora es necesario modificar el flujo de trabajo para que, si un usuario autenticado realiza una solicitud no autorizada, se envía a la página UnauthorizedAccess.aspx en lugar de a la página de registro. La lógica que redirige las solicitudes no autorizadas a la página de inicio de sesión se encuentra dentro de un método privado de la clase FormsAuthenticationModule, por lo que no podemos personalizar este comportamiento. Sin embargo, lo que podemos hacer es agregar nuestra propia lógica a la página de registro que redirige al usuario UnauthorizedAccess.aspx, si es necesario.

Cuando FormsAuthenticationModule redirige a un visitante no autorizado a la página de registro, anexa la dirección URL solicitada y no autorizada a la cadena de consulta con el nombre ReturnUrl. Por ejemplo, si un usuario no autorizado intentó visitar OnlyTito.aspx, el FormsAuthenticationModule los redirigiría a Login.aspx?ReturnUrl=OnlyTito.aspx. Por lo tanto, si un usuario autenticado llega a la página de registro con una cadena de consultas que incluye el parámetro ReturnUrl, sabemos que este usuario no autenticado acaba de intentar visitar una página que no está autorizada para ver. En tal caso, queremos redirigirla a UnauthorizedAccess.aspx.

Para ello, agregue el código siguiente al controlador de eventos Page_Load de la página de registro:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        if (Request.IsAuthenticated && !string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
        // This is an unauthorized, authenticated request...
        Response.Redirect("~/UnauthorizedAccess.aspx");
    }
}

El código anterior redirige a los usuarios autenticados y no autorizados a la página UnauthorizedAccess.aspx. Para ver esta lógica en acción, visite el sitio como visitante anónimo y haga clic en el vínculo Crear cuentas de usuario en la columna izquierda. Esto le llevará a la página ~/Membership/CreatingUserAccounts.aspx, que en el paso 1 hemos configurado para permitir solo el acceso a Tito. Dado que los usuarios anónimos están prohibidos, el FormsAuthenticationModule nos redirige de nuevo a la página de registro.

En este momento somos anónimos, por lo que Request.IsAuthenticated devuelve false y no se redirige a UnauthorizedAccess.aspx. En su lugar, se muestra la página de inicio de sesión. Inicie sesión como usuario distinto de Tito, como Bruce. Después de escribir las credenciales adecuadas, la página de inicio de sesión nos redirige a ~/Membership/CreatingUserAccounts.aspx. Sin embargo, dado que esta página solo es accesible para Tito, no estamos autorizados para verlo y se devuelven rápidamente a la página de inicio de sesión. Sin embargo, esta vez Request.IsAuthenticated devuelve true (y el parámetro querystring de ReturnUrl existe), por lo que se redirige a la página UnauthorizedAccess.aspx.

Authenticated, Unauthorized Users are Redirected to UnauthorizedAccess.aspx

Figura 6: Autenticado, los usuarios no autorizados se redirigen a UnauthorizedAccess.aspx (haga clic para ver la imagen en tamaño completo)

Este flujo de trabajo personalizado presenta una experiencia de usuario más sensible y sencilla al cortocircuitar el ciclo que se muestra en la figura 2.

Paso 3: Limitar la funcionalidad basada en el usuario que ha iniciado sesión actualmente

La autorización de direcciones URL facilita la especificación de reglas de autorización generales. Como vimos en el paso 1, con la autorización de dirección URL podemos indicar concisamente qué identidades se permiten y cuáles se deniegan de ver una página determinada o todas las páginas de una carpeta. Sin embargo, en determinados escenarios, es posible que deseemos permitir que todos los usuarios visiten una página, pero limite la funcionalidad de la página en función del usuario que lo visite.

Considere el caso de un sitio web de comercio electrónico que permite a los visitantes autenticados revisar sus productos. Cuando un usuario anónimo visita la página de un producto, solo vería la información del producto y no se le daría la oportunidad de dejar una revisión. Sin embargo, un usuario autenticado que visita la misma página vería la interfaz de revisión. Si el usuario autenticado aún no había revisado este producto, la interfaz les permitiría enviar una revisión; de lo contrario, mostraría su revisión enviada anteriormente. Para seguir este escenario, la página del producto podría mostrar información adicional y ofrecer características extendidas para aquellos usuarios que trabajan para la empresa de comercio electrónico. Por ejemplo, la página del producto podría enumerar el inventario en existencias e incluir opciones para editar el precio y la descripción del producto cuando un empleado visita.

Estas reglas de autorización detalladas se pueden implementar mediante declaración o mediante programación (o mediante alguna combinación de los dos). En la sección siguiente veremos cómo implementar una autorización específica mediante el control LoginView. Después, exploraremos técnicas de programación. Sin embargo, para poder examinar la aplicación de reglas de autorización específicas, primero es necesario crear una página cuya funcionalidad depende del usuario que lo visite.

Vamos a crear una página que muestre los archivos de un directorio determinado dentro de GridView. Junto con enumerar el nombre, el tamaño y otra información de cada archivo, GridView incluirá dos columnas de LinkButtons: una titulada View y una titulada Delete. Si se hace clic en View LinkButton (Ver LinkButton), se mostrará el contenido del archivo seleccionado; Si se hace clic en Delete LinkButton, se eliminará el archivo. Vamos a crear inicialmente esta página de modo que su funcionalidad de vista y eliminación esté disponible para todos los usuarios. En las secciones Using the LoginView Control and Programmatically Limiting Functionality (Usar el control LoginView y limitar la funcionalidad mediante programación), veremos cómo habilitar o deshabilitar estas características en función del usuario que visita la página.

Nota:

La página de ASP.NET que estamos a punto de compilar usa un control GridView para mostrar una lista de archivos. Dado que esta serie de tutoriales se centra en la autenticación, autorización, cuentas de usuario y roles de formularios, no quiero dedicar demasiado tiempo a analizar los trabajos internos del control GridView. Aunque en este tutorial se proporcionan instrucciones paso a paso específicas para configurar esta página, no profundiza en los detalles de por qué se realizaron determinadas opciones o qué efecto tienen las propiedades concretas en la salida representada. Para obtener un examen completo del control GridView, consulte mi tutorialTrabajar con datos en la serie ASP.NET 2.0.

Para empezar, abra el archivo UserBasedAuthorization.aspx en la carpeta Membership y agregue un control GridView a la página denominada FilesGrid. En la etiqueta inteligente de GridView, haga clic en el vínculo Editar columnas para iniciar el cuadro de diálogo Campos. Desde aquí, desactive la casilla Generar campos automáticamente en la esquina inferior izquierda. A continuación, agregue un botón Seleccionar, un botón Eliminar y dos BoundFields desde la esquina superior izquierda (los botones Seleccionar y Eliminar se pueden encontrar en el tipo CommandField). Establezca la propiedad SelectText del botón Seleccionar en Ver y las propiedades HeaderText y DataField del primer BoundField en Nombre. Establezca la segunda propiedad HeaderText de BoundField en Size en Bytes, su propiedad DataField en Length, su propiedad DataFormatString en {0:N0} y su propiedad HtmlEncode para False.

Después de configurar las columnas de GridView, haga clic en Aceptar para cerrar el cuadro de diálogo Campos. En la ventana Propiedades, establezca la propiedad DataKeyNames GridView en FullName. En este momento, el marcado declarativo de GridView debe tener un aspecto similar al siguiente:

<asp:GridView ID="FilesGrid" DataKeyNames="FullName" runat="server" AutoGenerateColumns="False">
 <Columns>
 <asp:CommandField SelectText="View" ShowSelectButton="True"/>
 <asp:CommandField ShowDeleteButton="True" />
 <asp:BoundField DataField="Name" HeaderText="Name" />
 <asp:BoundField DataField="Length" DataFormatString="{0:N0}"
 HeaderText="Size in Bytes" HtmlEncode="False" />
 </Columns>
</asp:GridView>

Con el marcado de GridView creado, estamos listos para escribir el código que recuperará los archivos de un directorio determinado y los enlazará a GridView. Agregue el código siguiente al controlador de eventos de la página Page_Load:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        string appPath = Request.PhysicalApplicationPath;
        DirectoryInfo dirInfo = new DirectoryInfo(appPath);

        FileInfo[] files = dirInfo.GetFiles();

        FilesGrid.DataSource = files;
        FilesGrid.DataBind();
    }
}

El código anterior usa la DirectoryInfo clase para obtener una lista de los archivos de la carpeta raíz de la aplicación. El GetFiles() método devuelve todos los archivos del directorio como una matriz de FileInfo objetos, que se enlaza a GridView. El objeto FileInfo tiene una variedad de propiedades, como Name, Length, y IsReadOnly, entre otros. Como puede ver en su marcado declarativo, GridView muestra solo las propiedades Name y Length.

Nota:

Las clases DirectoryInfo y FileInfo se encuentran en el System.IOespacio de nombres. Por lo tanto, deberá anteponer estos nombres de clase con sus nombres de espacio de nombres o tener el espacio de nombres importado en el archivo de clase (a través de using System.IO).

Dedique un momento a visitar esta página a través de un explorador. Mostrará la lista de archivos que residen en el directorio raíz de la aplicación. Al hacer clic en cualquiera de los elementos View o Delete LinkButtons, se producirá un postback, pero no se producirá ninguna acción porque todavía tenemos que crear los controladores de eventos necesarios.

The GridView Lists the Files in the Web Application's Root Directory

Figura 7: GridView enumera los archivos en el directorio raíz de la aplicación web (haga clic para ver la imagen en tamaño completo)

Necesitamos un medio para mostrar el contenido del archivo seleccionado. Vuelva a Visual Studio y agregue un TextBox denominado FileContents encima de GridView. Establezca la propiedad TextMode en MultiLine y las propiedades Columns y Rows en 95 % y 10, respectivamente.

<asp:TextBox ID="FileContents" runat="server" Rows="10"
TextMode="MultiLine" Width="95%"></asp:TextBox>

A continuación, cree un controlador de eventos para el SelectedIndexChangedevento de GridView y agregue el código siguiente:

protected void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{
    // Open the file and display it
    string fullFileName = FilesGrid.SelectedValue.ToString();
    string contents = File.ReadAllText(fullFileName);
    FileContents.Text = contents;
}

Este código usa la propiedad SelectedValue de GridView para determinar el nombre de archivo completo del archivo seleccionado. Internamente, se hace referencia a la colección DataKeys para obtener el SelectedValue, por lo que es imperativo establecer la propiedad DataKeyNames de GridView en Name, como se ha descrito anteriormente en este paso. La Fileclase se usa para leer el contenido del archivo seleccionado en una cadena, que a continuación se asigna a la propiedadFileContentsTextBoxText, mostrando así el contenido del archivo seleccionado en la página.

The Selected File's Contents are Displayed in the TextBox

Figura 8: El contenido del archivo seleccionado se muestra en TextBox (Haga clic para ver la imagen en tamaño completo)

Nota:

Si ve el contenido de un archivo que contiene el marcado HTML y a continuación, intenta ver o eliminar un archivo, recibirá un HttpRequestValidationException. Esto ocurre porque en postback el contenido de TextBox se devuelve al servidor web. De manera predeterminada, ASP.NET genera un error HttpRequestValidationException cada vez que se detecta contenido de postback potencialmente peligroso, como el marcado HTML. Para deshabilitar este error al producirse, desactive la validación de solicitudes para la página agregando ValidateRequest="false" a la directiva @Page. Para obtener más información sobre las ventajas de la validación de solicitudes, así como las precauciones que debe tomar al deshabilitarla, lea Validación de solicitudes: prevención de ataques de script .

Por último, agregue un controlador de eventos con el código siguiente para el RowDeletingevento GridView:

protected void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    string fullFileName = FilesGrid.DataKeys[e.RowIndex].Value.ToString();
    FileContents.Text = string.Format("You have opted to delete {0}.", fullFileName);

    // To actually delete the file, uncomment the following line
    // File.Delete(fullFileName);
}

El código simplemente muestra el nombre completo del archivo que se va a eliminar en FileContentsTextBoxsineliminar realmente el archivo.

Clicking the Delete Button Does Not Actually Delete the File

Figura 9: Al hacer clic en el botón Eliminar no se elimina realmente el archivo (Haga clic para ver la imagen en tamaño completo)

En el paso 1 hemos configurado las reglas de autorización de direcciones URL para impedir que los usuarios anónimos vean las páginas de la carpeta Membership. Para mostrar mejor la autenticación detallada, vamos a permitir que los usuarios anónimos visiten la páginaUserBasedAuthorization.aspx, pero con funcionalidad limitada. Para que todos los usuarios tengan acceso a esta página, añada el siguiente elemento <location> al archivo Web.config de la carpetaMembership:

<location path="UserBasedAuthorization.aspx">
 <system.web>
 <authorization>
 <allow users="*" />
 </authorization>
 </system.web>
</location>

Después de agregar este elemento <location>, pruebe las nuevas reglas de autorización de dirección URL cerrando la sesión en el sitio. Como usuario anónimo se le debería permitir visitar la página UserBasedAuthorization.aspx.

Actualmente, cualquier usuario autenticado o anónimo puede visitar la página UserBasedAuthorization.aspx y ver o eliminar archivos. Hagámoslo de forma que solo los usuarios autentificados puedan ver el contenido de un archivo y solo Tito pueda eliminar un archivo. Estas reglas de autorización detalladas se pueden aplicar mediante declaración, mediante programación o mediante una combinación de ambos métodos. Vamos a usar el enfoque declarativo para limitar quién puede ver el contenido de un archivo; Usaremos el enfoque mediante programación para limitar quién puede eliminar un archivo.

Uso del control LoginView

Como hemos visto en los tutoriales anteriores, el control LoginView es útil para mostrar diferentes interfaces para usuarios autenticados y anónimos, y ofrece una manera fácil de ocultar la funcionalidad que no es accesible para los usuarios anónimos. Dado que los usuarios anónimos no pueden ver o eliminar archivos, solo es necesario mostrar el TextBox FileContents cuando un usuario autenticado visita la página. Para conseguirlo, añada un control LoginView a la página, nómbrelo LoginViewForFileContentsTextBox, y mueva el marcado declarativo FileContents de TextBox al LoggedInTemplate del control LoginView.

<asp:LoginView ID=" LoginViewForFileContentsTextBox " runat="server">
 <LoggedInTemplate>
 <p>
 <asp:TextBox ID="FileContents" runat="server" Rows="10"
 TextMode="MultiLine" Width="95%"></asp:TextBox>
 </p>
 </LoggedInTemplate>
</asp:LoginView>

Los controles Web de las plantillas de LoginView ya no son accesibles directamente desde la clase de código subyacente. Por ejemplo, los controladores de eventos FilesGrid y SelectedIndexChanged de RowDeleting GridView hacen referencia actualmente al control FileContents de TextBox con código como:

FileContents.Text = text;

Sin embargo, este código ya no es válido. Al mover el TextBox FileContents al LoggedInTemplate no se puede acceder directamente al control TextBox. En su lugar, debemos utilizar el método FindControl("controlId") para hacer referencia al control mediante programación. Actualice los controladores de eventos de FilesGrid para hacer referencia a TextBox de la siguiente manera:

TextBox FileContentsTextBox = LoginViewForFileContentsTextBox.FindControl("FileContents") as TextBox;
FileContentsTextBox.Text = text;

Después de mover el TextBox al LoggedInTemplatedel LoginView y actualizar el código de la página para hacer referencia al TextBox utilizando el patrón FindControl("controlId"), visite la página como usuario anónimo. Como muestra la Figura 10, el TextBox FileContents no se muestra. Sin embargo, todavía se muestra LinkButton de Vista.

The LoginView Control Only Renders the FileContents TextBox for Authenticated Users

Figura 10: El control LoginView solo representa el TextBox FileContents para usuarios autenticados (haga clic para ver la imagen en tamaño completo)

Una forma de ocultar el botón Vista para usuarios anónimos es convertir el campo GridView en un TemplateField. Esto generará una plantilla que contiene el marcado declarativo para view LinkButton. Podemos entonces añadir un control LoginView al TemplateField y colocar el LinkButton en el LoggedInTemplate del LoginView, ocultando así el botón Vista a los visitantes anónimos. Para ello, haga clic en el vínculo Editar columnas de la etiqueta inteligente de GridView para iniciar el cuadro de diálogo Campos. A continuación, seleccione el botón Seleccionar de la lista situada en la esquina inferior izquierda y haga clic en el vínculo Convertir este campo en un TemplateField. Al hacerlo, se modificará el marcado declarativo del campo desde:

<asp:CommandField SelectText="View" ShowSelectButton="True"/>

A:

<asp:TemplateField ShowHeader="False">
 <ItemTemplate>
 <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"
 CommandName="Select" Text="View"></asp:LinkButton>
 </ItemTemplate>
</asp:TemplateField>

En este punto, podemos agregar un elemento LoginView a TemplateField. El marcado siguiente muestra view LinkButton only for authenticated users (Ver linkButton solo para usuarios autenticados).

<asp:TemplateField ShowHeader="False">
 <ItemTemplate>
 <asp:LoginView ID="LoginView1" runat="server">
 <LoggedInTemplate>
 <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"
 CommandName="Select" Text="View"></asp:LinkButton>
 </LoggedInTemplate>
 </asp:LoginView>
 </ItemTemplate>
</asp:TemplateField>

Como se muestra en la figura 11, el resultado final no es tan bonito como la columna Vista todavía se muestra aunque los LinkButtons de Vista de la columna estén ocultos. En la siguiente sección veremos cómo ocultar toda la columna GridView (y no sólo el LinkButton).

The LoginView Control Hides the View LinkButtons for Anonymous Visitors

Figura 11: el control LoginView oculta los botones ver LinkButtons para visitantes anónimos (Haga clic para ver la imagen en tamaño completo)

Limitación de la funcionalidad mediante programación

En algunas circunstancias, las técnicas declarativas no son suficientes para limitar la funcionalidad a una página. Por ejemplo, la disponibilidad de determinadas funcionalidades de página puede depender de criterios más allá de si el usuario que visita la página es anónimo o autenticado. En tales casos, los distintos elementos de la interfaz de usuario pueden mostrarse u ocultarse a través de medios se programación.

Para limitar la funcionalidad mediante programación, es necesario realizar dos tareas:

  1. Determinar si el usuario que visita la página puede acceder a la funcionalidad y
  2. Modifique mediante programación la interfaz de usuario en función de si el usuario tiene acceso a la funcionalidad en cuestión.

Para demostrar la aplicación de estas dos tareas, solo permitiremos que Tito elimine los archivos de GridView. Entonces, nuestra primera tarea, es determinar si Tito está visitando la página. Una vez que se haya determinado, es necesario ocultar (o mostrar) la columna Eliminar de GridView. Las columnas del GridView son accesibles a través de su propiedad Columns; una columna solo se muestra si su propiedad Visible está establecida true (el valor predeterminado).

Agregue el código siguiente al controlador de eventos Page_Load antes de enlazar los datos a GridView:

// Is this Tito visiting the page?
string userName = User.Identity.Name;
if (string.Compare(userName, "Tito", true) == 0)
    // This is Tito, SHOW the Delete column
    FilesGrid.Columns[1].Visible = true;
else
    // This is NOT Tito, HIDE the Delete column
    FilesGrid.Columns[1].Visible = false;

Como se describe en el tutorial An Overview of Forms Authentication (Introducción a la autenticación de formularios), User.Identity.Name devuelve el nombre de la identidad. Corresponde al nombre de usuario introducido en el control Inicio de sesión. Si es Tito que visita la página, la propiedad Visible de la segunda columna de GridView se establece en true; de lo contrario, se establece en false. El resultado neto es que cuando alguien distinto de Tito visita la página, ya sea otro usuario autenticado o un usuario anónimo, la columna Eliminar no se representa (vea la figura 12); sin embargo, cuando Tito visita la página, la columna Eliminar está presente (vea la figura 13).

The Delete Column is Not Rendered When Visited By Someone Other Than Tito (Such as Bruce)

Figura 12: La columna Eliminar no se representa cuando alguien que no sea Tito (por ejemplo, Bruce) (haga clic para ver la imagen en tamaño completo)

The Delete Column is Rendered for Tito

Figura 13: La columna Eliminar se representa en relación a Tito (haga clic para ver la imagen en tamaño completo)

Paso 4: Aplicar reglas de autorización basadas en roles a clases y métodos

En el Paso 3 no se le permite a los usuarios anónimos ver el contenido de un archivo y a todos los usuarios excepto Tito eliminar archivos. Esto se consiguió ocultando los elementos de interfaz de usuario asociados para visitantes no autorizados mediante técnicas declarativas y de programación. En nuestro ejemplo sencillo, ocultar correctamente los elementos de la interfaz de usuario era sencillo, pero ¿qué ocurre con sitios más complejos en los que puede haber muchas maneras diferentes de realizar la misma funcionalidad? Al limitar esa funcionalidad a usuarios no autorizados, ¿qué ocurre si olvidamos ocultar o deshabilitar todos los elementos de la interfaz de usuario aplicables?

Una manera fácil de asegurarse de que un usuario no autorizado pueda tener acceso a una determinada funcionalidad es decorar esa clase o método con el PrincipalPermissionatributo. Cuando el entorno de ejecución de .NET usa una clase o ejecuta uno de sus métodos, comprueba que el contexto de seguridad actual tiene permiso para usar la clase o ejecutar el método. El atributo PrincipalPermission proporciona un mecanismo a través del cual podemos definir estas reglas.

Vamos a demostrar el uso del atributo PrincipalPermission en el GridView SelectedIndexChanged y controlador de eventos RowDeleting para prohibir la ejecución por parte de usuarios anónimos y usuarios distintos de Tito, respectivamente. Todo lo que necesitamos hacer es agregar el atributo adecuado en cada definición de función:

[PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
protected void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{
    ...
}

[PrincipalPermission(SecurityAction.Demand, Name="Tito")]
protected void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    ...
}

El atributo del controlador de eventos SelectedIndexChanged determina que solo los usuarios autenticados pueden ejecutar el controlador de eventos, mientras que el atributo del controlador de eventos RowDeleting limita la ejecución a Tito.

Si, de alguna manera, un usuario distinto de Tito intenta ejecutar el controlador de eventos RowDeleting o un usuario no autenticado intenta ejecutar el controlador de eventos SelectedIndexChanged, el entorno de ejecución de .NET generará un SecurityException.

If the Security Context is not Authorized to Execute the Method, a SecurityException is Thrown

Figura 14: Si el contexto de seguridad no está autorizado para ejecutar el método, se produce una SecurityException (haga clic para ver la imagen en tamaño completo)

Nota:

Para permitir que varios contextos de seguridad accedan a una clase o método, decore la clase o el método con un atributo PrincipalPermission para cada contexto de seguridad. Es decir, para permitir que Tito y Bruce ejecuten el controlador de eventos RowDeleting, agregue dos atributosPrincipalPermission:

[PrincipalPermission(SecurityAction.Demand, Name="Tito")]

[PrincipalPermission(SecurityAction.Demand, Name="Bruce")]

Además de las páginas ASP.NET, muchas aplicaciones también tienen una arquitectura que incluye varias capas, como la lógica de negocios y las capas de acceso a datos. Estas capas normalmente se implementan como bibliotecas de clases y ofrecen clases y métodos para realizar la funcionalidad relacionada con la lógica de negocios y los datos. El atributo PrincipalPermission también es útil para aplicar reglas de autorización a estas capas.

Para obtener más información sobre el uso del atributo PrincipalPermission para definir reglas de autorización en clases y métodos, consulte la entrada de blog de Scott GuthrieCómo agregar reglas de autorización a capas de datos y empresariales mediante PrincipalPermissionAttributes.

Resumen

En este tutorial hemos visto cómo aplicar reglas de autorización basadas en el usuario. Empezamos con un vistazo a ASP. Marco de autorización de direcciones URL de NET. En cada solicitud, el UrlAuthorizationModule del motor de ASP.NET inspecciona las reglas de autorización de dirección URL definidas en la configuración de la aplicación para determinar si la identidad está autorizada para acceder al recurso solicitado. En resumen, la autorización de direcciones URL facilita la especificación de reglas de autorización para una página específica o para todas las páginas de un directorio determinado.

El marco de autorización de direcciones URL aplica reglas de autorización página por página. Con la autorización de dirección URL, la identidad solicitante está autorizada para acceder a un recurso determinado o no. Sin embargo, muchos escenarios requieren reglas de autorización más específicas. En lugar de definir quién tiene permiso para acceder a una página, es posible que tengamos que permitir que todos accedan a una página, pero mostrar datos diferentes o ofrecer funcionalidades diferentes en función del usuario que visita la página. La autorización a nivel de página generalmente implica ocultar elementos específicos de la interfaz de usuario para evitar que usuarios no autorizados accedan a funciones prohibidas. Además, es posible usar atributos para restringir el acceso a las clases y la ejecución de sus métodos para determinados usuarios.

¡Feliz programación!

Lecturas adicionales

Para obtener más información sobre los temas tratados en este tutorial, consulte los siguientes recursos:

Acerca del autor

Scott Mitchell, autor de varios libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, formador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Se puede contactar con Scott en mitchell@4guysfromrolla.com o a través de su blog en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores que fueron de gran ayuda. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame mitchell@4GuysFromRolla.com.