Autenticación por formularios en ASP.NETLunes, 19 Oct, 2009 @ 10:59 | Por Dario Krapp | AJAX, ASP.NET, Seguridad |
Evolución
Este modo de funcionamiento que hemos visto, propio de las versiones 1.x del Framework fue mejorado en la versión 2.0 donde se introdujeron cambios que permitieron automatizar ya no solo la autenticación contra cualquier repositorio, sino que también las operaciones (creación, modificación, eliminación, cambios de clave, etc.) de los usuarios involucrados en la misma, lo cual es bastante bueno ya que esas tareas son en realidad también bastante repetitivas.
Todos estos avances pudieron realizarse gracias al esquema de membrecía, más conocido como Membership (En el Framework 2.0 Se ha incluido además de la membrecía, la posibilidad de utilizar roles y perfiles de usuario).
La membrecía además de ofrecer código desarrollado, testeado y mantenido para el manejo de usuarios, brinda la posibilidad de utilizar controles ya incluidos en ASP.NET para realizar una variada cantidad de operaciones y la posibilidad de utilizar como repositorios un motor de base de datos SQL Server, Active Directory o un repositorio personalizado (donde deberá obviamente incluirse el código necesario para su funcionamiento). El siguiente esquema muestra a los actores involucrados y como los mismos se relacionan.

En el esquema puede observarse la existencia de los niveles: repositorios, proveedores, API de membrecía y controles incluidos en ASP.NET relacionados con la membrecía.
El primer nivel (repositorios) hace referencia al lugar donde la información de membrecía será almacenada. El segundo nivel (proveedores) está compuesto por clases dentro del Framework con la capacidad de establecer un enlace entre los repositorios del nivel inferior y la API del nivel superior.
El tercer nivel estará compuesto por la API de membrecía y el último nivel constituido por controles ya existentes en ASP.NET capaces de realizar las operaciones de membrecía y autenticación más frecuentes.
Bajo este esquema de funcionamiento la API en el tercer nivel podrá utilizar cualquier proveedor sin importar su implementación ya que todos los proveedores heredarán una clase en común llamada ProviderBase, brindándole a la API los miembros necesarios para que la misma pueda operar unívocamente con cualquiera de ellos.
Para el caso de SQL Server y Active Directory las implementaciones ya se encontrarán desarrolladas mientras que para realizar otras implementaciones será necesario crear una clase que herede de ProviderBase y se deberán sobreescribir los métodos necesarios apropiadamente.
En nuestro ejemplo utilizaremos como repositorio un motor SQL Server 2008 Enterprise Edition y como la implementación del proveedor SQL Server ya se encuentra desarrollada, simplemente deberemos preparar el repositorio y establecer el proveedor para que la API de membrecía lo utilice.
Antes que nada limpiaremos las líneas que habíamos agregado en el archivo web.config y dejaremos solo las siguientes:
<authentication mode="Forms"> <forms loginUrl="ingreso1.aspx"/> </authentication> <authorization> <deny users="?"/> </authorization>
El primer paso, como mencionamos previamente es el de preparar el repositorio, y considerando que estamos utilizando la opción de SQL Server, esto significará crear la base de datos, tablas, procedimientos almacenados, funciones, etc. para cada uno de los servicios que utilizararemos, para ser consistentes con la terminología utilizada, denominaremos a la membrecía como un servicio que instalaremos sobre el repositorio, (aunque en nuestro caso serán un conjunto de componentes de bases de datos). Además del servicio de membrecía existen otros servicios que también pueden instalarse, como indicaremos a continuación.
Un detalle es que en el caso de SQL Server Express 2005, el proveedor (SqlMembershipProvider) será capaz de instalar el servicio de membrecía automáticamente, pero en nuestro caso deberemos efectuar la tarea manualmente, aunque no es muy desalentador ya que la misma podrá efectuarse ejecutando por línea de comandos un comando llamado aspnet_regsql ajustando sus parámetros según nuestras necesidades. A continuación se describen los parámetros disponibles:
- -S : Nombre del servidor/instancia SQL Server que deberá ser de la versión 7.0 o superior
- -U: nombre de usuario
- -P:Clave del usuario
- -E: indicará específicamente que se utilizará autenticación integrada vía Windows, por lo que se omitirán los parámetros U y P. Cabe considerarse que si los parámetros U y P no son establecidos el comando asumirá automáticamente la autenticación vía Windows.
- -C: permitirá establecer el string de conexión ya sea por OLEDB u ODBC
- -sqlexportonly: Creará los scripts pero no los ejecutará.
- -A: Indicará que servicios deberán agregarse al repositorio, los cuales son membrecía, roles, perfiles, personalización para páginas con web parts y proveedor de eventos.Es posible instalar todos los servicios a la vez utilizando la opción all o cada servicio individualmente utilizando las opciones m, r, p, c y w para las opciones de membrecía, roles, perfiles, web parts y eventos respectivamente, en este articulo veremos la opción de membrecía y haremos algunos comentarios sobre la opción de roles.
- -R: Indica que servicios deberán removerse, en contraposición a la opción A y acepta las mismas opciones (all, m, r, p, c y w)
- -d: permite definir el nombre de la base de datos donde se instalarán los servicios. Tomándose el nombre aspnetdb por defecto.
Para nuestro ejemplo ejecutaremos el comando de la siguiente forma:

Se ha utilizado la dirección:
<unidad:>\WINDOWS\Microsoft.NET\Framework\v2.0.50727
Ya que la versión 3.0 y 3.5 son en realidad upgrades de la versión 2.0 por lo que el archivo aspnet_regsql no se encontrará en carpetas de versiones posteriores, debido a que no ha sido modificado desde la versión 2.0.
Para la utilización de membrecía será suficiente definir el parámetro –A con el valor “m”, esto indicaría que solo instalaremos los servicios de membrecía. Hemos incluido también los servicios de roles (letra “r”), como comentamos previamente, por que haremos unas referencias adicionales en el artículo.
Si luego ingresamos a nuestro servidor de base de datos veremos que el comando ha creado la base de datos pruebamembership y la estructura que el proveedor SqlMembershipProvider utilizará.
Si el archivo aspnet_regsql es ejecutado sin parámetros, se mostrará un asistente (Wizard) que permitirá configurar los parámetros que hemos visto previamente en una ventana visualmente.
Con el repositorio construido, el segundo paso será configurar a ASP.NET para que utilice el repositorio, esto implica hacer que ASP.NET utilice el proveedor SqlMembershipProvider con la configuración de acceso a la base de datos que hemos creado. Este paso es bastante simple y consta de crear una cadena de conexión y configurar el proveedor. Por simplicidad, he definido la cadena de conexión desde las propiedades del proyecto de la siguiente forma:

Luego de haberse definido la cadena de conexión deberá configurarse el proveedor para que utilice esa cadena de conexión. Esta tarea se realizará dentro del mismo archivo web.config y constará en definir los tags membership y provider (dentro de system.web).
En nuestro caso utilizaremos la siguiente configuración:
<membership defaultProvider="PruebamembershipProvider">
<providers>
<add name="PruebamembershipProvider" connectionStringName="FAYMS.Properties.Settings.pruebamembership"
applicationName=" Pruebamembership 1"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="false"
passwordFormat="Hashed"
minRequiredPasswordLength="4"
minRequiredNonalphanumericCharacters="0"
type="System.Web.Security.SqlMembershipProvider" />
</providers>
</membership>
En estas líneas hemos agregado a la membrecía el proveedor que hemos llamado PruebamembershipProvider que es del tipo SqlMembershipProvider y utiliza la conexión FAYMS.Properties.Settings.pruebamembership (que es la conexión que hemos definido desde las propiedades del proyecto previamente).
Para cada proveedor existirá además de los dos parámetros que hemos comentado una gran variedad de parámetros de configuración, que determinarán el comportamiento del mismo, para quien esté interesado en conocer todas las posibilidades, les dejo el siguiente enlace:
Propiedades de SqlMembershipProvider
Una vez realizados estos pasos la membrecía ha quedado terminada y lista para su uso.
Visual Studio ofrecerá incluso una herramienta denominada Web Site Administration Tool o simplemente WAT capaz entre otras cosas de operar con proveedores de membrecía. Si deseamos por ejemplo crear un usuario sobre el proveedor que hemos definido recientemente (PruebamembershipProvider) simplemente deberemos seleccionar la opción ASP.NET Configuration del Menú Project y aparecerá la aplicación WAT como se muestra a continuación:

Si seleccionamos la opción Security

Podremos por ejemplo crear un nuevo usuario seleccionando la opción Create user, donde un formulario de alta de usuarios se mostrará en pantalla, el comportamiento del mismo dependerá de las opciones parametrizadas en el proveedor. En nuestro caso según los parámetros configurados tomará el siguiente formato:

El botón Create User Creará el usuario en el repositorio, como puede verse a continuación:

Si dentro de la WAT cliqueamos la solapa Provider y la opción Select a different provider for each feature (advanced) veremos que se encuentra el proveedor que hemos definido junto al proveedor por defecto.

Nuestro proveedor ha quedado seleccionado ya que de esa forma lo hemos definido en el atributo defaultProvider del tag membership, en cuanto al proveedor ya incluido en ASP.NET que puede verse en la imagen, que no utilizaremos en nuestro ejemplo, podremos removerlo de la siguiente forma:
<membership defaultProvider="PruebamembershipProvider">
<providers>
<remove name="AspNetSqlMembershipProvider" />
<add name="PruebamembershipProvider"
……..
Luego de tanto trabajo, aunque sin una sola línea de código, lo mínimo que esperaríamos es poder autenticar a Usuario1 tal como lo habíamos hecho hace unos cuantos párrafos atrás sin el uso de membrecía.
Si en la antigua pantalla de Ingreso1.aspx modificamos el código de siguiente forma:
protected void Login_Click(object sender, EventArgs e)
{
if (Membership.ValidateUser(Usuario.Text, Clave.Text))
FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
else
LabelError.Text = "Usuario o clave incorrecto";
}
Y luego intentamos ingresar, veremos que todo funcionará según lo esperado. Como podemos ver, la validación del usuario ahora se efectuará mediante la API de membrecía, mientras que el manejo de la autenticación seguirá bajo el control de la clase FormsAuthentication.
La API de membrecía ofrecerá un amplio conjunto de métodos, además de ValidateUser se encontrarán CreateUser, DeleteUser, FindUsersByEmail, FindUsersByName, GeneratePassword, GetAllUsers, GetNumberOfUsersOnline, GetUser, GetUserNameByEmail y UpdateUser, donde sus nombres definen claramente su funcionalidad. La API de membrecía también ofrecerá unas cuantas propiedades y el evento ValidatingPassword que se disparará cuando un usuario es creado o su clave es modificada o reestablecida (reset).
Un objeto del tipo MembershipUser será devuelto por el método GetUser y mientras que un MembershipUserCollection será devuelto en los métodos FindUsersByEmail, FindUsersByName y GetAllUsers. La clase MembershipUser permitirá obtener información del usuario que representa (como por ejemplo: Nombre de usuario, Email, etc.) a través de sus propiedades. MembershipUser también contendrá métodos para realizar algunas operaciones como por ejemplo restablecer la clave de un usuario (reset) o modificarla.
Vuelvo a insistir en el hecho de que una ventaja muy interesante de la implementación de membrecía es que el uso de herencia y configuraciones en el web.config ha permitido un alto nivel de abstracción, el método ValidateUser de la clase Membership desconoce sobre que repositorio se está trabajando. Este desacople entre la API de membrecía y el manejo de los repositorios ha dado lugar a la creación de controles capaces de efectuar operaciones rutinarias de membrecía y autenticación en forma automática. Todo el esquema de autenticación que habíamos organizado en la página Ingreso1.aspx podrá reemplazarse con un solo control incluido ASP.NET y llamado adecuadamente Login.
Si reemplazamos los controles que habíamos agregado en la página Ingreso1.aspx por lo siguiente:
<asp:Login
runat="server"
ID="Login1"
RememberMeText="Recordar"
UserNameLabelText="Usuario"
PasswordLabelText="Clave"
TitleText="Ingreso a la aplicación"
FailureText="Usuario o clave incorrecto"
LoginButtonText="Ingresar"
DisplayRememberMe="true">
</asp:Login>
Obtendremos el mismo resultado con la ventaja de validadores incorporados, la opción de recordar al usuario incluida, código testeado, mantenido y listo para usar. Otros controles incorporados son LoginStatus, LoginView, PasswordRecovery, ChangePassword y el interesante CreateUserWizard cuyos nombres nos dan una idea inequívoca de sus funcionalidades.
Ahora que hemos conseguido hacer andar a la autenticación, posiblemente será un buen momento para retomar un detalle que habíamos dejado sujeto con alfileres, el cual es el tag authorization. En su momento lo definimos de la siguiente forma:
<authorization> <deny users="?"/> </authorization>
Donde indicábamos que los usuarios anónimos debían denegarse. El formato general del tag es el siguiente:
<authorization> <[allow|deny] users roles verbs /> </authorization>
Donde los tags allow o deny representarán reglas de acceso( a través de las operaciones de “permitir” o “denegar”) a los recursos del sitio. El atributo users representará a un grupo de usuarios y aceptará cuentas de usuario tanto como los simbolos “?” y “*” representando el símbolo “?” a los usuarios anónimos (no logueados) y “*” a todos los usuarios, de tal forma al especificar:
<deny users="?"/>
Se estaba indicando que se denegara el acceso a los recursos del sitio a usuarios anónimos, bajo estas circunstancias, cuando el browser intentase acceder a la página Default.aspx que es específicamente un recurso dentro de la carpeta principal del sitio, con un usuario aún no autenticado, el acceso sería denegado.
Es claro que en ningún lado especificamos que si debe permitirse el acceso a usuarios autenticados, la forma de especificarlo explícitamente sería la siguiente:
<authorization> <deny users="?"/> <allow users="*"/> </authorization>
Esto implica en primer lugar que es posible utilizar varios tag allow o deny como reglas de acceso, en segundo lugar que son procesados en orden, o sea la regla deny se aplica primero, y para quienes hayan superado la regla luego se aplicará la regla allow (si invirtiéramos las reglas veríamos que cualquier usuario podría ingresar al sitio dejando a la regla deny posterior sin sentido) y en tercero y último lugar, esto implica que existe un allow * implícito (solo es necesario acceder a
<unidad:>\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\web.config
para confirmarlo, comentario aparte, en este archivo se movieron algunas cuantas definiciones existentes en el archivo machine.config de las versiones 1.x), por ese motivo si eliminamos el tag authorization cualquier usuario podrá ingresar y también podremos omitir el tag allow que escribimos explícitamente en el último ejemplo.
De forma similar podríamos haber utilizado la regla:
<authorization> <allow users="Usuario1"/> <deny users="*"/> </authorization>
Si agregamos nuevos usuarios, además de Usuario1 que ya existía previamente e intentamos ingresar a Default.aspx veremos que solo Usuario1 puede hacerlo, los intentos con otros usuarios fallarán, y esto es lo que hemos especificado en las reglas de autorización, primero permitir el acceso al recurso pedido para Usuario1, y de quienes no cumplan la regla primera, ejecutar la segunda regla, que indica que para todos ellos, negar el acceso al recurso. Es posible incluir a varios usuarios separados por coma en el atributo users y evitarnos escribir unos cuantos tags.
Las reglas de autorización regirán para la carpeta del sitio tanto como para las subcarpetas del mismo, supongamos por ejemplo que nuestro sitio posee una subcarpeta de imágenes como se muestra en el ejemplo:

Y en nuestra página de login, Ingreso1.aspx agregamos lo siguiente:
<asp:Image runat="server" ImageUrl="Images/Logo.png" />
Si intentamos navegar la página Default.aspx seremos redireccionados a la página de login (esto se ha vuelto tan repetitivo que ya no debería sorprendernos) pero lo interesante es que veremos que la imagen aparece como inexistente en el browser aunque es claro que si existe.

Lo que sucede es que la imagen es también un recurso del sitio y la subcarpeta Images ha heredado las reglas de la carpeta padre, donde se establecía denegar el acceso a los usuarios no autenticados. Quien lo dude podrá simplemente probar autenticarse y luego volver a la página Ingreso1.aspx (sin desautenticarse) y la imagen aparecerá.
Es posible modificar las propiedades de las subcarpetas utilizando un tag llamado location, para nuestro ejemplo deberíamos escribir lo siguiente:
<location path="Images">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
El tag Location debemos definirlo dentro del tag Configuration y al mismo nivel del tag system.web del sitio y el mismo permitirá sobreescribir cualquier definición. En particular, para nuestro caso hemos sobreescrito la definición de autorización para que todo usuario pueda acceder a los recursos de la carpeta Images.
Además del atributo Users existe otro atributo llamado Verb en el tag authorization, que es opcional y permitirá especificar si la regla se aplicará a alguna acción de las siguientes GET, HEAD y POST. Por defecto el valor es “*” (para las tres acciones).
Finalmente veremos que existe la posibilidad de utilizar un atributo llamado roles (no es posible utilizar los atributos roles y users simultáneamente), la forma de uso es equivalente a los usuarios pero viendo esto podríamos preguntarnos ¿qué es un rol?.
Previamente habíamos comentado que el framework ofrecía además la posibilidad de incluir roles para los usuarios, la cual será seguramente una opción muy usual en escenarios de mundo real, comentaremos brevemente como utilizar roles con autenticación por formularios.
Para incluir roles en el esquema que hemos visto se ha adoptado una filosofía similar a la membrecía. Existirá en lugar de una API de membrecía una API de roles y un proveedor de roles, para nuestro caso, donde el repositorio era un servidor SQL Server 2008 Enterprise edition, en la creación del mismo, con la ayuda del comando aspnet_regsql hemos incluido los servicios de roles (incluyendo en el parámetro “-A” el valor “r”), así que solamente deberemos definir el proveedor de roles en el archivo web.config de la siguiente forma:
<roleManager enabled="true" defaultProvider="PruebaRolProvider">
<providers>
<remove name="AspNetSqlRoleProvider" />
<add connectionStringName="FAYMS.Properties.Settings.pruebamembership"
applicationName="PruebaRol1" name="PruebaRolProvider" type="System.Web.Security.SqlRoleProvider" />
</providers>
</roleManager>
Desde la WAT encontraremos una sección para el manejo de roles e inclusión de usuarios en los mismos. La API de roles por otra parte nos permitirá realizar varias tareas programáticamente. La ventaja de los roles radica en que se simplificarán las tareas de autorización y configuración de permisos, al igual que sucede en el propio sistema operativo.
Después de tanto visto, habrá seguramente quien se haya percatado de un detalle incómodo, ya que por lo visto hasta ahora hay ciertos datos del usuario (como por ejemplo el mail del mismo) que se encuentran incluidos en este esquema, pero es de esperarse que en cualquier escenario real puedan existir otros datos no hayan sido contemplados, y esta situación es inevitable ya que las posibilidades parecen infinitas. Por ejemplo, en algún tipo de aplicación podría requerirse almacenar el peso o la altura del usuario (para dar un ejemplo simple). Los perfiles o Profiles permitirán incluir información personalizada de los usuarios de la aplicación y una característica interesante es que este servicio no está ligado solamente a la autenticación por formularios, aunque como es de esperarse, funcionará perfectamente con ella. Los perfiles son tema bastante extenso del cual espero, podamos escribir algún artículo posteriormente, pero quería al menos comentar su existencia para que quién los necesite pueda desde este punto continuar investigando de qué se trata el tema.
Reciente









Viernes, 06 Noviembre, 2009 a las 10:36
Excelente guia funciona perfectamente, un detalle como se puede direccionar a una pagina .aspx específica de acuerdo al tipo de usuario despues de autenticarse?
Viernes, 06 Noviembre, 2009 a las 18:31
Muchas gracias Epifannio, el tema es que generalmente un usuario intenta acceder a una página y luego de la autenticación deseará ir a la pagina que había solicitado originalmente, a menos que haya ingresado a la página de login.
Para hacer que un usuario según alguna condición vaya a una página determinada, la forma que me parece de hacerlo es la siguiente:
if (Membership.ValidateUser("Usuario1", "Usuario1")){
FormsAuthentication.SetAuthCookie("Usuario1", false);
if (Roles.IsUserInRole("Usuario1", "Rol1"))
Response.Redirect("Default2.aspx");
else
Response.Redirect("Default3.aspx");
}
El usuario y clave están hardcodeados en este ejemplo para simplificarlo. El único punto a mencionar es que debés utilizar el método SetAuthCookie de la clase estática FormsAuthentication para establecer la cookie de autenticación (el segundo parámetro indicará si la cookie será o no persitetnte).
En este ejemplo si el usuario pertenece al rol “Rol1″ es redirigido a la pagina “Default2.aspx” y el caso contrario a “Default3.aspx”, pero la condición puede definirse según las necesidades de la aplicación.
Espero que la respuesta te haya servido.
Lunes, 09 Noviembre, 2009 a las 14:57
ok gracias Dario, en un tema que google no me ayudó bastante bien como tú es: Cómo imprimir los datos de un formulario web en asp.net? y mejor si es solo datos por que en los papeles membretados solo hace falta llenar campos vacios al imprimir.
Te cuento que cuando le doy con “window.print() de javascript” me imprime con cabeceras, pies, botones de la página.
Dario muchas gracias por tu ayuda..
Lunes, 09 Noviembre, 2009 a las 15:14
Epiffanio:
Para elegir que imprimir y que no de una página web puedes utilizar estilos, la clave es crear estilos distintos para lo que no quieras imprimir. Por ejemplo, supongamos que tenemos una página con un cuadro de texto y un botón, y sólo quieres que se imprima el cuadro de texto:
<INPUT TYPE="text" name="texto" value="Prueba" /><INPUT TYPE="submit" value="enviar"/>
Para que el segundo control no se imprima puedes crear un estilo dentro del medio “print”, para que sólo se aplique al imprimir:
@media print{
.noPrint
{
display: none;
}
}
BODY
{
overflow: hidden;
.....
Entonces, nuestro código HTML quedaría así:
<INPUT TYPE="text" name="texto" value="Prueba" /><INPUT TYPE="submit" value="enviar" class="noPrint" />
Si te fijas en el botón verás que le apliqué el estilo “noPrint” que, en el medio “Print”, hace que no se vea. Ten en cuenta que si quieres aplicar varios estilos a un mismo control puedes hacerlo separando los nombres con espacios, por ejemplo: ‘class=”noPrint botonAzul”‘.
Espero que se entienda la explicación, cualquier otra duda te invito a nuestro foro donde intentaremos responderte a la brevedad: http://foro.scientia.com.ar.
Suerte!
Miércoles, 07 Julio, 2010 a las 21:13
pueden hacerlo en visual xfis n.n graxias