miércoles
may142008
Controlando perfiles de usuario con Struts
miércoles, mayo 14, 2008 at 11:03AM
La información que hay por Internet referente al tema es escueta y aunque se encuentran muchos artículos al final todos tienen un enlace a alguno de los dos siguientes, que son los únicos interesantes. Por supuesto, como no podría ser de otra manera, en inglés:
www.onjava.com/pub/a/onjava/2004/02/18/strutssecurity.html?page=last&x-order=date
www-128.ibm.com/developerworks/web/library/wa-appsec/?ca=dgr-
lnxw16ServletsTaglibsStrutsSecurity#listing2
Los dos los artículos lo primero que hacen es decir que struts no es una tecnología que lo haga todo, sino que pretende ser una plataforma básica abierta que sirva de base. Después te sueltan el royo de que en una aplicación que se precie se han de contemplar multitud de usuarios con diferentes roles y blablabla y por fin pasan a dar la solución, que es similar.
Mi solución:
No me voy a poner a traducir ninguno de los artículos, sino que os voy a contar la solución que estoy adoptando, que no es que sea una cosa novedosa, es la misma que la de los artículos, sólo que explicada por mí (de hecho la solución no es mía, sino que es la que hemos adoptado en un proyecto que estoy realizando con más personas).
Consideraciones iniciales:
Lo primero que tenemos que tener en cuenta es que necesitamos un modo de identificación de usuarios en el sistema (login, password). Por este motivo supondremos para la realización de las restricciones que la identificación se ha realizado y que en session tenemos un objeto usuario (que será un registro u objeto que represente dicho registro de la tabla usuarios a la que haremos referencia posteriormente).
Como a priori no hemos definido ni los diferentes roles que tendrán los usuarios de la aplicación, ni las secciones en las que se dividirá nuestra aplicación ni los diferentes tipos de acceso que tendremos; y además, lo ideal sería que en caso de tener que modificar alguna de estas cosas fuera lo más rápido posible, la solución que vamos a ver consiste en tener una base de datos (o una serie de tablas dentro de la base de datos que usemos en la aplicación, si usamos alguna) donde almacenaremos toda esta información.
Además tendremos dos niveles de restricción, área de acceso (si el usuario puede o no acceder a la página) y tipo de operación (qué operaciones puede realizar el usuario de las disponibles en la página). Por ejemplo, si tenemos una página con una serie de datos editables habrá usuarios que no puedan acceder a ella, los habrá que puedan acceder pero no cambiar los datos (modo lectura) y los habrá que puedan acceder y cambiar los datos (modo lectura/escritura).
Requisitos de información:
Vamos a ver las tablas que necesitamos incluir en la base de datos y para qué sirve cada una. Evidentemente, dependiendo de los tipos de usuarios que se vallan a tener en la aplicación puede que tengamos que meter alguna tabla más o quitar alguna porque no sea necesaria; pero más o menos será de una forma parecida a esta.
- Usuarios
Entidad que recogerá los datos de los usuarios de la aplicación. En ella guardamos los datos personales de los usuarios, el identificador y la contraseña, y asociamos al usuario un perfil, que será el que defina los permisos.
PK: clave primaria UK: clave única FK: clave ajena
- Perfiles
Entidad que recogerá los perfiles de uso de la aplicación. Realmente la tabla es poca cosa, pero nos da un idPerfil que será el que usemos en los distintos permisos.
- Tipo de acceso
Entidad que recogerá los tipos de acceso, que pueden ser de creación, edición, eliminación, consulta...
- Secciones de la aplicación
Entidad que recogerá las distintas secciones de la aplicación sobre las que queremos definir permisos de acceso. Por ejemplo en una aplicación para una tienda podemos tener una zona de para el almacén, otra para los trabajadores de la tienda y otra para los clientes.
- Permisos
Entidad que recogerá los permisos que los distintos perfiles tienen sobre las diferentes secciones. En esta tabla es donde asociamos el perfil con una sección de la aplicación y lo dotamos de una serie de permisos.
Implementación de las restricciones sobre el sistema
Anteriormente hemos dicho que hay dos tipos de restricción, de acceso y de operación. Es ahora cuando realizaremos una diferenciación entre ellas, a la hora de implementarlas.
- Restricción de acceso
Pasando ya al lenguaje de struts, podemos entender una restricción de acceso como la posibilidad del usuario en cuestión de ver o no el resultado de un determinado Action, ya que se supone que antes de ver una página determinada se habrá de ejecutar el correspondiente Action, que es el que genera los datos a mostrar en la página.
Lo que queremos entonces es que si un usuario tiene permisos se ejecute el Action y se pase a la página jsp correspondiente; o que en el caso de no tener permisos pasemos a una página de error. Lo vamos a realizar en tres pasos:
1-. Superclase para los Action
Partiendo de que tenemos varias secciones en la aplicación y que cada sección se encuentra implementada por un conjunto de Actino, vamos a colocar sobre los Action de cada sección una superclase que en su método execute lo primero que compruebe sean los permisos de acceso del usuario. Sería una cosa más o menos así:
De tal forma que los Action de la sección heredarán de este y será de la forma:
2-. Extender ActionMapping
Extenderemos la clase ActionMapping de struts para darle más atributos, ya que interesará tener para cada Action datos relativos a la sección a la que pertenece, así que le añadiremos el atributo applicationZone.
A partir de ahora, el parámetro de tipo ActionMapping del método execute de los Action será de tipo SecurityActionMapping (podremos hacer un casting dentro del método a un objeto de esta clase con el objeto de entrada). Pero para hacer que esto sea así, antes hemos de completar el siguiente paso.
3-. Inclusión del atributo en el struts-config.xml
En el fichero struts-config.xml modificamos las definiciones de los objetos Action incluyendo el atributo className=ruta_de_paquetes.SecurityActionMapping y una directiva set-property. De la siguiente forma
El código de sección deberá coincidir el valor del atributo codigo de algún registro de la base de datos de la tabla de secciones de la aplicación.
- Restricción de operación
Ya hemos dicho que larestricción de operación se puede entender cómo la necesidad de un determinado perfil para disponer de determinadas opciones o controles en laspáginas JSP que se visualizan, como pueden ser permisos de edición o no de algunos datos. (por supuesto, si un usuario tiene restringido el botón editar por poner un ejemplo de una página jsp, también deberá tener restringida la ejecución del Action que realiza la edición, mediante una restricción de acceso)
Las restricciones de operación deberán tener reflejo en las páginas JSP que muestran la información y los controles para operar sobre ella; por ejemplo, si no se pueden editar los datos, los campos de edición deberán aparecer en sólo lectura y el botón de grabar debe estar oculto. Lo haremos en dos pasos:
1-. Colocación de permisos de operación en request
En el Action que implementa la restricción de acceso correspondiente se establecerán en ámbito de request los permisos de operación que el usuario que hay en session tiene en la sección del Action que se invocó para llegar hasta el JSP. Los permisos posibles dependerán realmente de cómo sea nuestra aplicación y de, por supuesto, los permisos que tenga el perfil del usuario.
Por ejemplo: Si el usuario tiene permisos para modificar datos, se ha de establecer en ámbito de request el atributo canEdit a true
request.setAttribute(canEdit, new Bolean(true));
2-. Restricción en las páginas JSP
En las páginas JSP se han de restringir la aparición o la funcionalidad de los diferentes controles dependiendo de los permisos que tengamos establecidos en request. Por ejemplo, si tenemos canEdit el botón de edición deberá aparecer y los campos ser editables; pero si no tenemos canDelete, el botón de borrar deberá permanecer oculto (por supuesto el usuario no podrá tener acceso al Action que realiza dicho borrado mediante una restricción de acceso).
Anexo: De la excepción AccesoNoPermitidoException a la pantalla de error
Antes hemos usado la excepción AccesoNoPermitidoException, que sería de la forma:
Esto lo que provoca es un fallo en la llamada al servidor, por lo que se devuelve la típica página de error. Evidentemente queremos que la página que se muestre sea distinta, una del estilo no tiene permisos para realizar la acción solicitada. Contacte con el administrador.... Además, de camino vamos a tener la posibilidad de tener nuestra propia página de error para la aplicación.
¿Cómo lo haremos? De una forma fácil: vamos a crear un manejador de excepciones, lo vamos a poner como manejador de excepciones de la aplicación y vamos a crear nuestra página de error y nuestra página de acceso no permitido.
El manejador de errores va a ser una clase del estilo:
El método lo que ha de hacer es redireccionar al correspondiente forward (que posteriormente incluiremos en el struts-config.xml). Como queremos diferenciar entre un acceso no permitido y una excepción, según lo que sea (usando la condición if(exception.getClass().equals(AccesoNoPermitidoException.class)) vemos si es un acceso no permitido) redireccionamos a un sitio o a otro. Además, si es una excepción metemos en request dicha excepción, por si luego la queremos mostrar en la pantalla de error.
Para establecer nuestro manejador de excepciones como manejador por defecto incluimos en el struts-config.xml el siguiente código detrás de la sección form-beans:
Y para los forwards de error y acceso no permitido incluimos lo siguiente a continuación:
Por último tenemos que crear las páginas error.jsp y permisoDenegado.jsp
Bueno, pues hemos llegado al final...
www.onjava.com/pub/a/onjava/2004/02/18/strutssecurity.html?page=last&x-order=date
www-128.ibm.com/developerworks/web/library/wa-appsec/?ca=dgr-
lnxw16ServletsTaglibsStrutsSecurity#listing2
Los dos los artículos lo primero que hacen es decir que struts no es una tecnología que lo haga todo, sino que pretende ser una plataforma básica abierta que sirva de base. Después te sueltan el royo de que en una aplicación que se precie se han de contemplar multitud de usuarios con diferentes roles y blablabla y por fin pasan a dar la solución, que es similar.
Mi solución:
No me voy a poner a traducir ninguno de los artículos, sino que os voy a contar la solución que estoy adoptando, que no es que sea una cosa novedosa, es la misma que la de los artículos, sólo que explicada por mí (de hecho la solución no es mía, sino que es la que hemos adoptado en un proyecto que estoy realizando con más personas).
Consideraciones iniciales:
Lo primero que tenemos que tener en cuenta es que necesitamos un modo de identificación de usuarios en el sistema (login, password). Por este motivo supondremos para la realización de las restricciones que la identificación se ha realizado y que en session tenemos un objeto usuario (que será un registro u objeto que represente dicho registro de la tabla usuarios a la que haremos referencia posteriormente).
Como a priori no hemos definido ni los diferentes roles que tendrán los usuarios de la aplicación, ni las secciones en las que se dividirá nuestra aplicación ni los diferentes tipos de acceso que tendremos; y además, lo ideal sería que en caso de tener que modificar alguna de estas cosas fuera lo más rápido posible, la solución que vamos a ver consiste en tener una base de datos (o una serie de tablas dentro de la base de datos que usemos en la aplicación, si usamos alguna) donde almacenaremos toda esta información.
Además tendremos dos niveles de restricción, área de acceso (si el usuario puede o no acceder a la página) y tipo de operación (qué operaciones puede realizar el usuario de las disponibles en la página). Por ejemplo, si tenemos una página con una serie de datos editables habrá usuarios que no puedan acceder a ella, los habrá que puedan acceder pero no cambiar los datos (modo lectura) y los habrá que puedan acceder y cambiar los datos (modo lectura/escritura).
Requisitos de información:
Vamos a ver las tablas que necesitamos incluir en la base de datos y para qué sirve cada una. Evidentemente, dependiendo de los tipos de usuarios que se vallan a tener en la aplicación puede que tengamos que meter alguna tabla más o quitar alguna porque no sea necesaria; pero más o menos será de una forma parecida a esta.
- Usuarios
Entidad que recogerá los datos de los usuarios de la aplicación. En ella guardamos los datos personales de los usuarios, el identificador y la contraseña, y asociamos al usuario un perfil, que será el que defina los permisos.
| Atributo | Tipo | BBDD | Comentario |
| idUsuario (PK) | numérico | not null | Código del usuario |
| login (UK) | texto | not null | Login en la aplicación |
| password | texto | Null | Contraseña del usuario |
| nombre | texto | Null | Nombre del usuario |
| apellido | texto | Null | Apellidos del usuario |
| ... | Null | Resto de datos personales... | |
| idPerfil (FK) | numérico | not null | Id del Perfil que define los permisos del usuario. |
PK: clave primaria UK: clave única FK: clave ajena
- Perfiles
Entidad que recogerá los perfiles de uso de la aplicación. Realmente la tabla es poca cosa, pero nos da un idPerfil que será el que usemos en los distintos permisos.
| Atributo | Tipo | BD | Comentario |
| idPerfil (PK) | numérico | not null | Código del perfil |
| perfil | texto | not null | Nombre del perfil |
- Tipo de acceso
Entidad que recogerá los tipos de acceso, que pueden ser de creación, edición, eliminación, consulta...
| Atributo | Tipo | BBDD | Comentario |
| idTipoAcceso (PK) | numérico | not null | Código del tipo de Acceso |
| codigo | Numérico | not null | Código constante para identificar el tipo de acceso |
| tipoAcceso | texto | not null | Descripción del tipo de acceso |
- Secciones de la aplicación
Entidad que recogerá las distintas secciones de la aplicación sobre las que queremos definir permisos de acceso. Por ejemplo en una aplicación para una tienda podemos tener una zona de para el almacén, otra para los trabajadores de la tienda y otra para los clientes.
| Atributo | Tipo | BBDD | Comentario |
| idSeccionAplicacion (PK) | numérico | not null | Código de la Sección de la Aplicación |
| codigo | texto | not null | Código constante para identificar la sección |
| seccionAplicacion | texto | not null | Descripción de la sección |
- Permisos
Entidad que recogerá los permisos que los distintos perfiles tienen sobre las diferentes secciones. En esta tabla es donde asociamos el perfil con una sección de la aplicación y lo dotamos de una serie de permisos.
| Atributo | Tipo | BBDD | Comentario |
| idPermiso (PK) | numérico | not null | Código del permiso |
| idTipoAcceso(FK) | numérico | not null | Código del tipo de acceso |
| idSeccionAplicacion(FK) | numérico | not null | Código de la sección de la aplicación |
| idPerfil(FK) | numérico | not null | Código del perfil |
Implementación de las restricciones sobre el sistema
Anteriormente hemos dicho que hay dos tipos de restricción, de acceso y de operación. Es ahora cuando realizaremos una diferenciación entre ellas, a la hora de implementarlas.
- Restricción de acceso
Pasando ya al lenguaje de struts, podemos entender una restricción de acceso como la posibilidad del usuario en cuestión de ver o no el resultado de un determinado Action, ya que se supone que antes de ver una página determinada se habrá de ejecutar el correspondiente Action, que es el que genera los datos a mostrar en la página.
Lo que queremos entonces es que si un usuario tiene permisos se ejecute el Action y se pase a la página jsp correspondiente; o que en el caso de no tener permisos pasemos a una página de error. Lo vamos a realizar en tres pasos:
1-. Superclase para los Action
Partiendo de que tenemos varias secciones en la aplicación y que cada sección se encuentra implementada por un conjunto de Actino, vamos a colocar sobre los Action de cada sección una superclase que en su método execute lo primero que compruebe sean los permisos de acceso del usuario. Sería una cosa más o menos así:
public abstract class RestriccionAction extends Action
{
public ActionForward execute(.....)
{
doRestriccion( );
return _execute( );
}
public abstract ActionForward _execute( .);
private void doRestriccion( ) throws AccesoNoPermitidoException
{
/* Este metodo realiza las comprobaciones de acceso con
el perfil del usuario que haya en sesión */
}
}
De tal forma que los Action de la sección heredarán de este y será de la forma:
public class CuarquierCosaAction extends RestriccionAction
{
public ActionForward _execute(.....)
{
/* Este es el método execute que tenía el Action, pero le cambiamos el nombre
para que se ejecute el de la clase padre que implementa la restricción */
}
}
2-. Extender ActionMapping
Extenderemos la clase ActionMapping de struts para darle más atributos, ya que interesará tener para cada Action datos relativos a la sección a la que pertenece, así que le añadiremos el atributo applicationZone.
public class SecurityActionMapping extends ActionMapping
{
private String zone = null;
public String getApplicationZone()
{
return zone;
}
public void setApplicationZone(String newZone)
{
zone = newZone;
}
}
A partir de ahora, el parámetro de tipo ActionMapping del método execute de los Action será de tipo SecurityActionMapping (podremos hacer un casting dentro del método a un objeto de esta clase con el objeto de entrada). Pero para hacer que esto sea así, antes hemos de completar el siguiente paso.
3-. Inclusión del atributo en el struts-config.xml
En el fichero struts-config.xml modificamos las definiciones de los objetos Action incluyendo el atributo className=ruta_de_paquetes.SecurityActionMapping y una directiva set-property. De la siguiente forma
path=...
[...]
className="ruta_de_paquetes.SecurityActionMapping"
[...] >
property="applicationZone"
value="CODIGO_DE_SECCION" />
[...] />
>
El código de sección deberá coincidir el valor del atributo codigo de algún registro de la base de datos de la tabla de secciones de la aplicación.
- Restricción de operación
Ya hemos dicho que larestricción de operación se puede entender cómo la necesidad de un determinado perfil para disponer de determinadas opciones o controles en laspáginas JSP que se visualizan, como pueden ser permisos de edición o no de algunos datos. (por supuesto, si un usuario tiene restringido el botón editar por poner un ejemplo de una página jsp, también deberá tener restringida la ejecución del Action que realiza la edición, mediante una restricción de acceso)
Las restricciones de operación deberán tener reflejo en las páginas JSP que muestran la información y los controles para operar sobre ella; por ejemplo, si no se pueden editar los datos, los campos de edición deberán aparecer en sólo lectura y el botón de grabar debe estar oculto. Lo haremos en dos pasos:
1-. Colocación de permisos de operación en request
En el Action que implementa la restricción de acceso correspondiente se establecerán en ámbito de request los permisos de operación que el usuario que hay en session tiene en la sección del Action que se invocó para llegar hasta el JSP. Los permisos posibles dependerán realmente de cómo sea nuestra aplicación y de, por supuesto, los permisos que tenga el perfil del usuario.
Por ejemplo: Si el usuario tiene permisos para modificar datos, se ha de establecer en ámbito de request el atributo canEdit a true
request.setAttribute(canEdit, new Bolean(true));
2-. Restricción en las páginas JSP
En las páginas JSP se han de restringir la aparición o la funcionalidad de los diferentes controles dependiendo de los permisos que tengamos establecidos en request. Por ejemplo, si tenemos canEdit el botón de edición deberá aparecer y los campos ser editables; pero si no tenemos canDelete, el botón de borrar deberá permanecer oculto (por supuesto el usuario no podrá tener acceso al Action que realiza dicho borrado mediante una restricción de acceso).
Anexo: De la excepción AccesoNoPermitidoException a la pantalla de error
Antes hemos usado la excepción AccesoNoPermitidoException, que sería de la forma:
public class AccesoNoPermitidoException extends Exception
{
public void printStackTrace()
{
super.printStackTrace();
}
}
Esto lo que provoca es un fallo en la llamada al servidor, por lo que se devuelve la típica página de error. Evidentemente queremos que la página que se muestre sea distinta, una del estilo no tiene permisos para realizar la acción solicitada. Contacte con el administrador.... Además, de camino vamos a tener la posibilidad de tener nuestra propia página de error para la aplicación.
¿Cómo lo haremos? De una forma fácil: vamos a crear un manejador de excepciones, lo vamos a poner como manejador de excepciones de la aplicación y vamos a crear nuestra página de error y nuestra página de acceso no permitido.
El manejador de errores va a ser una clase del estilo:
public class ExceptionHandler extends ExceptionHandler
{
public ActionForward execute(Exception exception, ExceptionConfig config,
ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws ServletException
{
if(exception.getClass().equals(AccesoNoPermitidoException.class))
{
return mapping.findForward("permisoDenegado");
}
else
{
request.setAttribute("exception",exception);
return mapping.findForward("error");
}
}
}
El método lo que ha de hacer es redireccionar al correspondiente forward (que posteriormente incluiremos en el struts-config.xml). Como queremos diferenciar entre un acceso no permitido y una excepción, según lo que sea (usando la condición if(exception.getClass().equals(AccesoNoPermitidoException.class)) vemos si es un acceso no permitido) redireccionamos a un sitio o a otro. Además, si es una excepción metemos en request dicha excepción, por si luego la queremos mostrar en la pantalla de error.
Para establecer nuestro manejador de excepciones como manejador por defecto incluimos en el struts-config.xml el siguiente código detrás de la sección form-beans:
>
key="lang.exception" type="java.lang.Exception"
handler="ruta_de_paquetes.ExceptionHandler" />
>
Y para los forwards de error y acceso no permitido incluimos lo siguiente a continuación:
>
name="error" path="/error.jsp" contextRelative="true" />
name="permisoDenegado" path="/permisoDenegado.jsp" contextRelative="true" />
>
Por último tenemos que crear las páginas error.jsp y permisoDenegado.jsp
Bueno, pues hemos llegado al final...
in
j2ee
j2ee 
Reader Comments