

- Autenticación: que consiste en identificar al usuario en el sistema y comprobar que el usuario es quien dice ser. Normalmente la autenticación se suele realizar pidiéndole al usuario su identificativo, nombre de usuario o correo electrónico y una contraseña que solo él conoce. Aunque hay otras formas de realizarlo entre ellas los certificados.
- Autorización: que consiste en determinar si el usuario autenticado tienen permisos para realizar una determinada operación. La autorización puede realizarse mediante roles, permisos o una combinación de ambos dependiendo de lo adecuado para la operación. Pero en ocasiones no solo hay que validar si un usuario tiene permisos para para realizar una acción, también puede ser necesario restringir la operación sobre ciertos datos, los que se determinen que él está autorizado a modificar, si no se hiciese esto un usuario podría alterar los datos de otro y el sistema tener una brecha de seguridad.
La información de autenticación y autorización puede guardarse en diferentes formas en lo que se conocen como Realms comunmente en Java. Algunos Realms puede ser simples archivos de texto plano aunque por su dificultad de mantenimiento al añadir nuevos usuarios, permisos o roles y que puede requerir un reinicio de la aplicación se suele optar por opciones como una base de datos relacional, un sistema LDAP o una base de datos nosql.
Para tener un sistema seguro no basta con ocultar las opciones que un usuario no puede realizar. Ocultar las opciones está bien pero también hay que realizar las comprobaciones de autorización en el caso de una aplicación web en el servidor, al igual que no basta con realizar las comprobaciones de validación de datos en el cliente con javascript, en ambos casos las comprobaciones hay que hacerlas en el lado del servidor también, de lo contrario nada impediría a un usuario conociendo la URL y datos adecuados a enviar realizar algo que no debería (advertido estás si no quieres que te llamen un sábado de madrugada).
La seguridad puede aplicarse de dos formas o una combinación de ambas:
- De forma declarativa: ya sea mediante anotaciones o en un archivo independiente del código. Esta es la opción preferida ya que de esta manera el código de la aplicación no está mezclado con el aspecto de la seguridad.
- De forma programática: si la opción declarativa no no es suficiente para algún caso podemos optar por hacerlo de forma programática, mediante código, con la que tendremos total flexibilidad para hacer cosas más específicas si necesitamos aunque mezclaremos el código de la aplicación con el código de seguridad.
Las dos librerías son similares aunque se comenta que Apache Shiro es más fácil de aprender. Además de integraciones con estas librerías Apache Tapestry dispone de módulos para realizar autenticación con servicios de terceros como Facebook, Twitter o sistemas OpenID.
Pero veamos como aplicar seguridad a una aplicación web que use el framework Apache Tapestry. En el ejemplo usaré el módulo tapestry-security que a su vez usa Apache Shiro. El ejemplo consiste un una página a la que solo los usuarios autenticados pueden acceder, en ella los usuarios podrán realizar varias operaciones: sumar a una cuenta uno, dos, tres, restar uno o poner la cuenta a cero en función de los permisos y roles que tengan. Para autenticase se usa un formulario aunque perfectamente podría usarse una autenticación BASIC.
Por simplicidad en el ejemplo los usuarios, passwords, roles y permisos los definiré en un archivo de texto, aunque en un proyecto real probablemente usaríamos una base de datos accediendo con hibernate para lo cual deberíamos implementar unos pocos métodos de la interfaz Realm o si necesitamos autorización la interfaz AuthorizingRealm de Shiro. El archivo shiro-users.properties sería el siguiente:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usuarios, passwords y roles | |
user.root = password,root | |
user.superuser = password,superuser | |
user.user = password,user | |
# Permisos de los roles | |
role.root = "cuenta:add2,substract1,reset" | |
role.superuser = "cuenta:add2" | |
#role.user = "" |
La única configuración que deberemos indicarle a Tapestry es la URL de la página que autenticará a los usuarios y la página a mostrar en caso de que el usuario no esté autorizado para realizar alguna operación y el Realm a usar, lo hacemos añadiendo el siguiente código al módulo de la aplicación:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void contributeWebSecurityManager(Configuration<Realm> configuration) { | |
ExtendedPropertiesRealm realm = new ExtendedPropertiesRealm("classpath:shiro-users.properties"); | |
configuration.add(realm); | |
} | |
public static void contributeSecurityConfiguration(Configuration<SecurityFilterChain> configuration, SecurityFilterChainFactory factory) { | |
configuration.add(factory.createChainWithRegEx("^*/login*$").add(factory.anon()).build()); | |
configuration.add(factory.createChainWithRegEx("^*/index*$").add(factory.user()).build()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.tapestry.security.pages; | |
import org.apache.shiro.authc.AuthenticationException; | |
import org.apache.shiro.authc.IncorrectCredentialsException; | |
import org.apache.shiro.authc.LockedAccountException; | |
import org.apache.shiro.authc.UnknownAccountException; | |
import org.apache.shiro.authc.UsernamePasswordToken; | |
import org.apache.shiro.subject.Subject; | |
import org.apache.tapestry5.annotations.Component; | |
import org.apache.tapestry5.annotations.Property; | |
import org.apache.tapestry5.corelib.components.Form; | |
import org.apache.tapestry5.ioc.annotations.Inject; | |
import org.tynamo.security.services.SecurityService; | |
public class Login { | |
@Property | |
private String usuario; | |
@Property | |
private String password; | |
@Inject | |
private SecurityService securityService; | |
@Component | |
private Form loginForm; | |
Object onActivate() { | |
// Si el usuario ya está autenticado redirigir a la página Index | |
if (securityService.isUser()) { | |
return Index.class; | |
} | |
return null; | |
} | |
Object onValidateFromLoginForm() { | |
if (loginForm.getHasErrors()) { | |
return null; | |
} | |
Subject subject = securityService.getSubject(); | |
if (subject == null) { | |
return null; | |
} | |
// Recolectar en el token los datos introducidos por el usuario | |
UsernamePasswordToken token = new UsernamePasswordToken(usuario, password); | |
token.setRememberMe(true); | |
try { | |
// Validar e iniciar las credenciales del usuario | |
subject.login(token); | |
} catch (UnknownAccountException e) { | |
loginForm.recordError("Cuenta de usuario desconocida"); | |
return null; | |
} catch (IncorrectCredentialsException e) { | |
loginForm.recordError("Credenciales inválidas"); | |
return null; | |
} catch (LockedAccountException e) { | |
loginForm.recordError("Cuenta bloqueada"); | |
return null; | |
} catch (AuthenticationException e) { | |
loginForm.recordError("Se ha producido un error"); | |
return null; | |
} | |
// Usuario autenticado, redirigir a la página Index | |
return Index.class; | |
} | |
} |
Si las anotaciones no son suficientes podemos hacerlo de forma programática, este es el probable caso de que un usuario solo debería modificar los datos relativos a él sin poder modificar los de otros usuarios. El código variará en función de la forma de determinar si el usuario tiene permisos para un dato. Para comprobar si un usuario tiene ciertos permisos de forma programática debemos usar el objeto Subject que tiene muchos métodos para realizar comprobaciones, como para reinicializar la cuenta se ha de tener el permiso «cuenta:reset» se debe hacer lo codificado en el método onActionFromReset:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.tapestry.security.pages; | |
import org.apache.shiro.SecurityUtils; | |
import org.apache.shiro.authz.annotation.RequiresPermissions; | |
import org.apache.shiro.authz.annotation.RequiresRoles; | |
import org.apache.shiro.authz.annotation.RequiresUser; | |
import org.apache.shiro.web.subject.support.WebDelegatingSubject; | |
import org.apache.tapestry5.SymbolConstants; | |
import org.apache.tapestry5.annotations.Persist; | |
import org.apache.tapestry5.annotations.Property; | |
import org.apache.tapestry5.ioc.annotations.Inject; | |
import org.apache.tapestry5.ioc.annotations.Symbol; | |
@RequiresUser | |
public class Index { | |
@Inject | |
@Symbol(SymbolConstants.TAPESTRY_VERSION) | |
@Property | |
private String tapestryVersion; | |
@Persist | |
@Property | |
private Long cuenta; | |
void setupRender() { | |
if (cuenta == null) { | |
cuenta = 0l; | |
} | |
} | |
public WebDelegatingSubject getSubject() { | |
return (WebDelegatingSubject) SecurityUtils.getSubject(); | |
} | |
@RequiresUser | |
public void onActionFromAdd1() { | |
cuenta += 1; | |
} | |
public void onActionFromSubstract1() { | |
// Esta operación es insegura porque aunque se oculta en la página, | |
// el ejecutar la operación no tiene ninguna restricción de permisos | |
// como en el caso de add2 o add3. Con el enlace adecuado cualquier usuario | |
// puede ejecutarla aunque no vea el enlace (http://localhost:8080/TapestrySecurity/index.substract1). | |
cuenta -= 1; | |
} | |
@RequiresPermissions("cuenta:add2") | |
public void onActionFromAdd2() { | |
cuenta += 2; | |
} | |
@RequiresRoles("root") | |
public void onActionFromAdd3() { | |
cuenta += 3; | |
} | |
public void onActionFromReset() { | |
SecurityUtils.getSubject().checkPermission("cuenta:reset"); | |
cuenta = 0l; | |
} | |
@RequiresUser | |
public void onActionFromCloseSesion() { | |
SecurityUtils.getSubject().logout(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html t:type="layout" | |
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" | |
xmlns:p="tapestry:parameter"> | |
Versión: <b>${tapestryVersion}</b><br/> | |
<t:holaMundo/><br/> | |
<br/> | |
Hola <b>${subject.principal}</b> ¿ke ase?,<br/> | |
<br/> | |
La cuenta es ${cuenta}.<br/> | |
<br/> | |
<ul style="list-style: none;"> | |
<li><t:actionlink t:id="add1">¿quieres sumar 1 a la cuenta? (requiere un usuario)</t:actionlink></li> | |
<t:security.haspermission permission="cuenta:add2"> | |
<li><t:actionlink t:id="add2">¿quieres sumar 2 a la cuenta? (requiere rol superuser)</t:actionlink></li> | |
</t:security.haspermission> | |
<t:security.hasrole role="root"> | |
<li><t:actionlink t:id="add3">¿quieres sumar 3 a la cuenta? (requiere rol root)</t:actionlink></li> | |
</t:security.hasrole> | |
<t:security.haspermission permission="cuenta:substract1"> | |
<li><t:actionlink t:id="substract1">¿quieres restar 1 a la cuenta? (requiere permiso substract, para visualizarse)</t:actionlink></li> | |
</t:security.haspermission> | |
<li><t:actionlink t:id="reset">¿quieres poner la cuenta a 0? (requiere permiso cuenta:reset)</t:actionlink></li> | |
<li><t:actionlink t:id="closeSesion">¿quieres cerrar la sesión?</t:actionlink></li> | |
</ul> | |
</html> |
Para hacer uso de tapestry-security deberemos incluir la librería como dependencia en el archivo build.gradle del proyecto:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
compile('org.tynamo:tapestry-security:0.5.1') { | |
exclude(group: 'org.apache.shiro') | |
} | |
compile 'org.apache.shiro:shiro-all:1.2.1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ git clone git://github.com/picodotdev/elblogdepicodev.git | |
$ cd elblogdepicodev/TapestrySecurity | |
$ ./gradlew tomcatRun | |
# Abrir en el navegador http://localhost:8080/TapestrySecurity/index |
Referencia:
Documentación sobre Apache Tapestry
http://tapestry.apache.org/security.html
http://tapestry.apache.org/
http://shiro.apache.org/
http://static.springsource.org/spring-security/site/index.html