martes, 10 de agosto de 2010

Spring Security Mediante Base de Datos

En el post anterior les mostré como implementar spring security de la manera más básica, ahora continuaremos pero esta vez ya no obtendremos los usuarios desde XML como en:














Ahora vamos a obtener los usuarios desde un archivo de propiedades, para eso el XML anterior se reduce a:










Ojo que el archivo de propiedades lo podemos obtener desde el classpath de la aplicación(como en el ejemplo anterior)
o desde un archivo externo a la aplicación como en:









De esta manera podemos modificar el archivo de propiedades sin desempaquetar el EAR o WAR de la aplicación


El archivo de propiedades que contiene a los usuarios es bien simple, le especificamos el usuario, password, los roles o perfiles y si está habilitado o no:


#usuario=password,rol1[,rolN][,enabled|disabled]
ccacique1=pass1,PERFIL_ADMINISTRADOR,disabled
ccacique2=pass2,PERFIL_SUPERVISOR,enabled
ccacique3=pass3,PERFIL_ADMINISTRADOR,PERFIL_SUPERVISOR,enabled


La gestión de usuarios mediante archivos(sea en XML o en properties) no es apropiado en un entorno de producción donde pueden interactuar cientos o miles de usuarios, por lo que spring security nos proporciona una manera de obtener dichos usuarios desde la base de datos:


Para implementar la gestión de usuarios mediante una base de datos con spring security debemos de hacer lo siguiente:


  1. Implementar un proveedor de usuarios.

  2. El proveedor de usuarios debe de implementar la interface org.springframework.security.core.userdetails.UserDetailsService

  3. La interface anterior obliga a definir el método public UserDetails loadUserByUsername(String username), por lo que debemos de implementarlo

  4. El método anterior retorna un objeto de la clase org.springframework.security.core.userdetails.UserDetails, esta clase define campos de autenticación de usuarios simple como nombre de usuario, password, si el usuario está bloqueado y los perfiles asociados a este usuario.

  5. Colocar la referencia al proveedor de usuarios en donde usuarioService es mi proveedor de usuarios



Veamos la implementación del gestor de usuarios:



package pe.com.slcsccy.pc.springsecurity.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import pe.com.slcsccy.pc.springsecurity.dao.UsuarioDAO;
import pe.com.slcsccy.pc.springsecurity.model.Usuario;

@Service("usuarioService")
public class UsuarioServiceImpl implements UserDetailsService{
@Autowired
private UsuarioDAO usuarioDAO;

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Usuario usuario = null;
List usuarios = usuarioDAO.buscarUsuarioPorCodigo(username);
if(usuarios.size()==0)throw new UsernameNotFoundException("Usuario no existe.");
else{
usuario = usuarios.get(0);
usuario.setPerfiles(usuarioDAO.buscarPerfilesDeUsuarioPorCodigo(username));
return usuario;
}
}
}


El proveedor de usuarios hace uso de un objeto de acceso a datos, que lo he implementado con spring jdbc:


package pe.com.slcsccy.pc.springsecurity.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.GrantedAuthority;

import pe.com.slcsccy.pc.springsecurity.model.Perfil;
import pe.com.slcsccy.pc.springsecurity.model.Usuario;

public class UsuarioDAOImpl implements UsuarioDAO {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

@SuppressWarnings("unchecked")
@Override
public List buscarUsuarioPorCodigo(String codigo) {
return jdbcTemplate.query("select * from usuario where cod_usuario = ? ", new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Usuario usuario = new Usuario();
usuario.setUsername(rs.getString("cod_usuario"));
usuario.setPassword(rs.getString("des_password"));
usuario.setAccountNonExpired(rs.getString("in_cuenta_expirada").equals("N")?true:false);
usuario.setAccountNonLocked(rs.getString("in_cuenta_bloqueada").equals("N")?true:false);
usuario.setCredentialsNonExpired(rs.getString("in_credencial_expirada").equals("N")?true:false);
usuario.setEnabled(rs.getString("in_habilitado").equals("S")?true:false);
return usuario;
}
},codigo);
}

@SuppressWarnings("unchecked")
@Override
public List buscarPerfilesDeUsuarioPorCodigo(String codigo) {
return jdbcTemplate.query("select cod_perfil from usuario_perfil where cod_usuario = ? ", new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Perfil perfil = new Perfil();
perfil.setNombrePerfil(rs.getString("cod_perfil"));
return perfil;
}
},codigo);
}
}


El acceso a los datos se realiza mediante un modelo de dominio de usuarios y de perfiles siguiente:

Usuario.java


package pe.com.slcsccy.pc.springsecurity.model;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class Usuario implements UserDetails{

private static final long serialVersionUID = -4993799715730712001L;

private List perfiles;
private String username;
private String password;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;

public Collection getPerfiles() {
return perfiles;
}

@SuppressWarnings("unchecked")
public void setPerfiles(List perfiles) {
this.perfiles = (List) perfiles;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Collection getAuthorities() {
return perfiles;
}

}



Perfil.java


package pe.com.slcsccy.pc.springsecurity.model;
import org.springframework.security.core.GrantedAuthority;
public class Perfil implements GrantedAuthority{

private static final long serialVersionUID = 1L;
private String nombrePerfil;

public Perfil(){
}

public String getNombrePerfil() {
return nombrePerfil;
}

public void setNombrePerfil(String nombrePerfil) {
this.nombrePerfil = nombrePerfil;
}

@Override
public String getAuthority() {
return this.nombrePerfil;
}

}


El modelo de dominio y el objeto de acceso a datos colaboran entre sí para conectarse y dar soporte a los datos de una base de datos Oracle que tiene la siguiente estructura e información:


create user springsecurity identified by springsecurity account unlock;
grant resource,connect to springsecurity;

create table springsecurity.usuario(
cod_usuario varchar2(40) not null,
des_password varchar2(40) not null,
in_cuenta_expirada char(1) default 'N' not null ,
in_cuenta_bloqueada char(1) default 'N' not null ,
in_credencial_expirada char(1) default 'N' not null ,
in_habilitado char(1) default 'S' not null ,
primary key(cod_usuario)
);

create table springsecurity.perfil(
cod_perfil varchar2(40) not null,
des_perfil varchar2(40) not null,
primary key(cod_perfil)
);

create table springsecurity.usuario_perfil(
cod_usuario varchar2(40),
cod_perfil varchar2(40),
primary key(cod_usuario,cod_perfil),
foreign key(cod_usuario) references springsecurity.usuario(cod_usuario),
foreign key(cod_perfil) references springsecurity.perfil(cod_perfil)
);

insert into springsecurity.perfil values('PERFIL_ADMINISTRADOR','PERFIL DE ADMINISTRADOR');
insert into springsecurity.perfil values('PERFIL_SUPERVISOR','PERFIL DE SUPERVISOR');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique1','pass1');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique2','pass2');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique3','pass3');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique4','pass4');
insert into springsecurity.usuario_perfil(cod_usuario,cod_perfil) values('ccacique1','PERFIL_ADMINISTRADOR');
insert into springsecurity.usuario_perfil(cod_usuario,cod_perfil) values('ccacique2','PERFIL_SUPERVISOR');



Con la estructura anterior ya podemos gestionar nuestros usuarios con soporte de spring security.


El contenedor de beans de spring security quedaría de la siguiente manera:



xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">


access-decision-manager-ref="accessDecisionManager">







login-page="/login.jsp"
default-target-url="/public/mostrarFormulario.htm"
authentication-failure-url="/loginerror.jsp"/>





invalidate-session="true"
logout-url="/public/terminarSesion.htm"
logout-success-url="/logout.jsp" />













































El proyecto completo te lo puedes bajar desde AQUI

8 comentarios:

  1. Gran tutorial,,, todo bien esplicado,,, aunque tal qual no e sido capaz de hacer funciona ya que no se spring mvc ni sabia mucho de spring security,, lo e sido capaz de integrar con Struts2,,,, Enorabuena,,, Saludos

    ResponderBorrar
  2. Carlos como estás? Mira estoy teniendo problemas con la base de datos oracle al querer lanzar la aplicación:

    Su autenticación a fallado, mensaje: StatementCallback; uncategorized SQLException for SQL [select * from ach_cops.usuario where cod_usuario = ? ]; SQL state [72000]; error code [1008]; ORA-01008: no todas las variables han sido enlazadas ; nested exception is java.sql.SQLException: ORA-01008: no todas las variables han sido enlazadas

    REINICIAR

    Sabes que pudiera ser?

    ResponderBorrar
  3. Por otro lado me esta generando un error en el método query del jdbcTemplate al parecer la documentación no existe el método con parametros como sql, rowmapper y el string del valor para el preparedstatement. EStoy ahciendo algo mal?

    ResponderBorrar
  4. Hola, tienes un buen e interesante blog, felicidades. Me gustaría poder enlazarlo para que quienes visiten mi blog, puedan informarse con los articulos que tiene. Por favor, si estás interesado contáctame a manganimemaster@gmail.com Saludos cordiales.

    ResponderBorrar
  5. hola que tal! permítame felicitarlo por su excelente blog, me encantaría tenerlo en mis blogs de tecnología. Estoy segura que su blog sería de mucho interés para mis visitantes !.Si puede sírvase a contactarme almodhena@gmail.com

    saludos

    ResponderBorrar
  6. hola, he estado visitando tu pagina y tienes contenido muy interesante, sigue posteando asi, me encantará volver, saludos!

    ResponderBorrar
  7. Hola, primero te agradezco por el gran aporte que realizas a la comunidad, muy buenos tutoriales tienes. Me gustaria que nos contaras tu experiencia con SpringSecurity y la manipulacion de las jsessionid.

    ResponderBorrar
  8. Buenas, necesitaria ayuda para integrar struts2 + spring security, estoy teniendo bastante problemas... agradeceria si me pueden ayudar con un ejemplo. Muchas gracias.
    Otra consulta: La base de datos de este ejemplo de donde la puedo sacar.
    Saludos!!

    ResponderBorrar

Es bueno comunicarnos, comenta!!.