Shiro es un marco de seguridad de código abierto que ha surgido recientemente en el ecosistema Java. Comparado con JAAS y Spring Security, Shiro ofrece una combinación equilibrada de potencia, simplicidad y flexibilidad. Este artículo explora los conceptos clave y el modelo de permisos de Shiro, junto con ejemplos prácticos de uso del framework y del plugin Grails Shiro Plugin. A lo largo del texto, el lector podrá apreciar las ventajas de Shiro.
Arquitectura genarel
La arquitectura de Shiro se compone de cuatro componentes principales:
- SecurityManager: Fachada típica que expone los servicios de gestión de seguridad.
- Authenticator: Verifica "¿Quién eres?" manejando principal y credenciales.
- Authorizer: Controla el acceso después de la autenticación, determinando "quién puede hacer qué".
- Session Manager: Gestiona sesiones de forma independiente del entorno, soportando clientes heterogéneos.

Estos componentes interactúan con fuentes de datos como JDBC, LDAP o Active Directory a través de Realms. El Session Manager puede persistir sesiones en caché, base de datos o sistema de archivos.
Modelo de seguridad
El modelo de seguridad de Shiro se basa en cinco conceptos fundamentales:
- Subject: Representa al usuario actual o a una aplicación.
- Principal: Identificador único del Subject (ej. nombre de usuario).
- Role y Permission: Representan niveles de granularidad de permisos. Los roles agrupan permisos, mientras que los permisos son atómicos.
- Realm: Componente responsable de la autenticación y autorización reales.

Para implementar la seguridad de manera eficaz, se deben seguir estos principios:
- Los roles son dinámicos y agrupan permisos.
- Los permisos son estáticos y forman la base del módulo de seguridad.
- Hacer permisos dinámicos es posible pero complejo y puede dispersar la lógica de autorización.
- Excepción: si se conoce la regla de definición dinámica (ej. en Grails:
${controllerName}:${actionName}:${params.id}), se pueden definir permisos dinámicamente.
Clases conceptuales clave

- AuthenticationToken y AuthenticationInfo: Antes y después de la autenticación, respectivamente. Contienen principal y credenciales.
- RememberMe: Estado opcional que no equivale a autenticación completa.
- Credentials y CredentialsMatcher: Las credenciales suelen almacenarse cifradas; el matcher compara la credencial ingresada con la almacenada.
- PAM (Pluggable Authentication Modules): Permite múltiples Realms con estrategias como AllSuccessful, AtLeastOneSuccessful o FirstSuccessful.
- AuthorizationInfo: Combina roles y permisos.
- PermissionResolver y Permission: Convierten permisos en cadenas (WildcardPermission) y realizan comparaciones mediante
implies().

WildcardPermission es una característica poderosa: los permisos se representan como cadenas jerárquicas (ej. message:update,delete:*). La comparación se basa en reglas de subcadenas y comodines.
- La estructura de la cadena es definida por el usuario, por convención:
recurso:acción:instancia. - No debe haber espacios entre los separadores para evitar inconsistencias.
Realm: Componente que accede a la fuente de datos real. Por ejemplo, JdbcRealm tiene la siguiente cadena de herencia:

Session: Conetxto asociado a un Subject. Shiro proporciona una sesión independiente del entorno; en web se usa HttpSession.
SecurityManager: Fachada que combina Authenticator + Authorizer + SessionFactory. Para aplicaciones web se usa DefaultWebSecurityManager.
Filter: En entornos web, Shiro utiliza un filtro principal que encadena filtros secundarios:

- En web.xml se configura
JSecurityFilter(oSpringJSecurityFilterpara Spring). - Los filtros secundarios se pasan como parámetros; el orden importa (de específico a general en URL, y AND para una misma URL).
Subject: Proporciona todas las operaciones de seguridad (autenticación y autorización) delegando en el SecurityManager.
Configuration: Ensambla los componentes y crea el SecurityManager, típicamente mediante archivos ini.

JSecurityFiltercrea una instancia de Configuraton usando el parámetroconfig.- En Spring,
SpringIniWebConfigurationrequiere el nombre del bean del SecurityManager (securityManagerBeanName).
SecurityUtils: Clase de utilidad para obtener Subject y SecurityManager.
Otros módulos: AOP, Cache, Codec, Crypto, IO, JNDI, util, etiquetas para web.
Uso típico con Grails
Ejemplo 1: Shiro desde cero
Implementaremos un sistema de mensajes donde solo usuarios autenticados y con permisos específicos puedan modificar o eliminar mensajes.
Clases de dominio de seguridad:
class CuentaUsuario {
String nombreUsuario
String contrasena
static hasMany = [roles: Rol]
static belongsTo = Rol
}
class Rol {
String nombreRol
static hasMany = [usuarios: CuentaUsuario, permisos: Permiso]
}
class Permiso {
String permiso
static hasMany = [roles: Rol]
static belongsTo = Rol
}
Clase Mensaje:
class Mensaje {
String contenido
CuentaUsuario usuario
}
Configuración de web.xml:
<filter>
<filter-name>filtroSeguridad</filter-name>
<filter-class>org.jsecurity.spring.SpringJSecurityFilter</filter-class>
<init-param>
<param-name>securityManagerBeanName</param-name>
<param-value>gestorSeguridad</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filtroSeguridad</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Realm en resources.groovy:
beans = {
comparadorCredenciales(org.jsecurity.authc.credential.Sha1CredentialsMatcher) {
storedCredentialsHexEncoded = true
}
resolvedorPermisos(org.jsecurity.authz.permission.WildcardPermissionResolver)
realm(org.jsecurity.realm.jdbc.JdbcRealm) {
permissionResolver = ref("resolvedorPermisos")
dataSource = ref("dataSource")
permissionsLookupEnabled = true
permissionsQuery = "SELECT p.permiso FROM permiso p, rol_permisos rp, rol r WHERE p.id = rp.permiso_id AND rp.rol_id = r.id AND r.nombre_rol = ?"
userRolesQuery = "SELECT r.nombre_rol FROM rol r, rol_usuarios ru, cuenta_usuario u WHERE r.id = ru.rol_id AND ru.usuario_id = u.id AND u.nombre_usuario = ?"
authenticationQuery = "SELECT contrasena FROM cuenta_usuario WHERE nombre_usuario = ?"
}
gestorSeguridad(org.jsecurity.web.DefaultWebSecurityManager) {
bean -> bean.destroyMethod = "destroy"
realms = [ref("realm")]
}
}
SecurityFilters:
import org.jsecurity.SecurityUtils
class SecurityFilters {
def filters = {
autenticacion(controller:'*', action:'*') {
before = {
if (controllerName != 'auth') {
def subject = SecurityUtils.subject
if (!subject.authenticated) {
redirect(controller: 'auth', action: 'login',
params: [targetUri: request.forwardURI - request.contextPath])
return false
}
}
}
}
admin(controller: 'cuentaUsuario|rol|permiso', action: '*') {
before = {
def subject = SecurityUtils.subject
if (!subject.hasRole('admin')) {
redirect(controller: 'auth', action: 'noAutorizado')
return false
}
}
}
editarMensaje(controller: 'mensaje', action: 'actualizar|eliminar') {
before = {
def subject = SecurityUtils.subject
if (!subject.isPermitted("${controllerName}:${actionName}:${params.id}")) {
redirect(controller: 'auth', action: 'noAutorizado')
return false
}
}
}
}
}
Código de autenticación:
def iniciarSesion = {
def token = new UsernamePasswordToken(params.nombreUsuario, params.contrasena)
if (params.recordarme) {
token.rememberMe = true
}
try {
gestorSeguridad.login(token)
def destino = params.targetUri ?: "/"
redirect(uri: destino)
} catch (AuthenticationException e) {
flash.message = "Error de autenticación"
redirect(action: 'login', params: [nombreUsuario: params.nombreUsuario])
}
}
Ejemplo 2: Usando el plugin Shiro de Grails
El plugin simplifica enormemente el proceso. Pasos:
- Instalar:
grails install-plugin shiro - Ejecutar:
grails quick-start(genera ShiroDbRealm, ShiroUser, ShiroRole, AuthController, SecurityFilters) - Agregar datos de prueba en BootStrap.groovy:
def init = { servletContext ->
def user = new ShiroUser(username: "admin",
passwordHash: new Sha1Hash("admin").toHex()).save()
def role = new ShiroRole(name: "admin").addToUsers(user).save()
}
SecurityFilters modificado:
auth(controller: "*", action: "*") {
before = { accessControl { true } }
}
gestion(controller: "shiroUser|shiroRole|shiroPermission", action: "*") {
before = { accessControl { role("admin") } }
}
mensaje(controller: "mensaje", action: "eliminar|actualizar") {
before = {
accessControl {
permission("mensaje:${actionName}:${params.id}")
}
}
}
Uso de tags en GSP:
<shiro:hasPermission permission="mensaje:actualizar,eliminar">
<g:actionSubmit class="edit" value="Editar"/>
<g:actionSubmit class="delete" value="Eliminar"
onclick="return confirm('¿Estás seguro?');"/>
</shiro:hasPermission>
Otros tags útiles: principal, hasRole, hasPermission, isLoggedIn, hasAnyRole.
Conclusión
Shiro proporciona una solución de seguridad flexible y sencilla para aplicaciones Java. Los ejemplos presentados muestran tanto la implementación manual como el uso del plugin Grails, que acelera el desarrollo. El código fuente de ambos ejemplos está disponible para su descarga.