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:
- Implementar un proveedor de usuarios.
- El proveedor de usuarios debe de implementar la interface org.springframework.security.core.userdetails.UserDetailsService
- La interface anterior obliga a definir el método public UserDetails loadUserByUsername(String username), por lo que debemos de implementarlo
- 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.
- 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