lunes, 4 de agosto de 2014

Creando un Realm personalizado para Shiro que permita leer los usuarios de una fuente externa


Como ya hemos vistos en alguno de los post anteriores Apache Shiro es una buena herramienta para las tareas de seguridad mas comunes dentro de una aplicación J2EE. Una de las formas que tiene este framework para permitirnos definir toda la configuración de la seguridad de nuestra aplicación de una manera declarativa y externa a la misma es mediante la creación de un archivo .ini con la siguiente estructura.

[main]
authc.loginUrl = /jsp/login.xhtml
authc.successUrl = /jsp/index.xhtml

[users]
admin=LECTOR
admin=EDITOR

[roles]
LECTOR=nota:leer
EDITOR=nota:crear,modificar

[urls]
/jsp/listaNotas.xhtml=perms["nota:leer"]
/jsp/insertarNota.xhtml=roles["EDITOR"]
/jsp/** = authc

Sin embargo, con frecuencia es posible rellenar de antemano todas las secciones anteriores excepto la de los usuarios. Es muy común que en las aplicaciones de hoy en día obtengamos la información de los usuarios a partir de otro tipo de recursos tales como una Base de Datos LDAP que sin duda nos brindan una mayor flexibilidad para el manejo de los cambios en los mismos (altas y bajas), así como mayor seguridad que un simple archivo de texto. ¿Pero que pasaría si no quisiésemos renunciar a la idea de definir nuestra configuración de seguridad en un archivo .ini pero necesitásemos obtener los usuarios de una fuente externa?

Excavando en la muy bien diseñada arquitectura de Shiro encontramos el concepto de Realm que es una interfaz para dado el nombre y contraseña del usuario actual recuperar la información de autenticación o autorización del mismo, dada que se cuenta con que esta almacenada en un sitio externo. Shiro posee varias clases que implementan esta interfaz para los casos mas comunes tales como :

  • AbstractLdapRealm
  • ActiveDirectoryRealm
  • JdbcRealm
  • JndiLdapRealm
  • IniRealm


Este ultimo es el Realm autorizado en el caso en que usemos un archivo ini. Mirando en el código fuente vemos que su funcionamiento interno consiste en parsear el archivo y extraer cada una de las secciones. Es de esta forma que nos es posible reutilizar esta clase a través de la herencia, pero sustituir en nuestra clase hija el fragmento en el que se cargan los usuarios por nuestra propia forma de obtenerlos. Un ejemplo sería como el que sigue:

...
public class IniConUsuarioExternoRealm extends IniRealm {

private static final String INI_FILE = "/WEB-INF/shiro.ini";

@Override
protected AuthenticationInfo doGetAuthenticationInfo

(AuthenticationToken token) throws AuthenticationException {
actualizarDatosDeUsuario();
return super.doGetAuthenticationInfo(token);
}

@Override
protected void onInit() {
this.setResourcePath("classpath:" + INI_FILE);

super.onInit();
}

public void actualizarDatosDeUsuario

(AuthenticationToken token) {
Map<String, Object> map = FacesContext.getCurrentInstance().

getExternalContext().getSessionMap();

String user = token.getUser();
String pass = token.getPs();
String[] roles = //Aqui obten los roles para tu usuario 

//de donde quiera que los tengas

if (StringUtils.isBlank(user)) {
throw new AuthorizationException("No hay usuarios ni roles");
}

construirCuenta(user, "", roles);
}

private SimpleAccount construirCuenta

(String username, String password, String[] roles) {
SimpleAccount account = getUser(username);
if (account == null) {
account = new SimpleAccount(username, password, getName());
add(account);
}

account.setCredentials(password);

if (roles != null) {
for (int i = 0; i < roles.length; i++) {
String rolename = roles[i];
account.addRole(rolename);

SimpleRole role = getRole(rolename);
if (role != null) {
account.addObjectPermissions(role.getPermissions());
}
}
} else {
account.setRoles(null);
}

return account;
}
}