Spring Security con Base de Datos

Un tutorial más de la serie Spring Security, seguiremos trabajando con el proyecto que hemos utilizado desde el principio del curso, esta vez nos enfocaremos en la tarea de integración de una base de datos en donde se almacenarán los usuarios registrados, usando estos datos realizaremos la autenticación y autorización requeridos para poder acceder a nuestro sitio web protegido.

Estudiaremos dos enfoques, primero utilizando JDBC y luego con JPA Hibernate, para la base de datos utilizaremos el motor HSQLDB, aunque fácilmente puede cambiar a MYSQL, SQL Server, o cualquier otro.

La BD que utilicemos deberá tener como mínimo las siguientes tablas, en USERS almacenamos el nombre de usuario, contraseña, y un booleano que indica si el usuario está activo o no, en la tabla AUTHORITIES guardamos los roles de cada usuario, el esquema básico es el siguiente:

image

Lo primero que requerimos es agregar las dependencias spring-jdbc y hsqldb.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.3.3</version>
</dependency>

Luego debemos configurar el origen de datos, puedes profundizar viendo los tutoriales: acceso a datos con spring-jdbc, bases datos con hibernate-jpa, la configuración es simple:

@Configuration
public class WebDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:schema.sql")
                .addScript("classpath:data.sql")
                .generateUniqueName(true)
                .build();
    }
}

Para que esta configuración sea utilizada por nuestro proyecto debemos indicarle a la clase WebAppInitializer que debe utilizar la misma, del mismo modo como lo hacemos con la clase de configuración de seguridad.

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{WebSecConfig.class, WebDataConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebAppConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

El archivo schema.sql es el encargado de crear las tablas.

CREATE TABLE USERS(
    USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    ENABLED BOOLEAN NOT NULL
);

CREATE TABLE AUTHORITIES(
    USERNAME VARCHAR_IGNORECASE(50) NOT NULL,
    AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS
    FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME)
);

El archivo data.sql agrega datos a las tablas previamente creadas.

INSERT INTO USERS VALUES('user', '123', true);
INSERT INTO AUTHORITIES VALUES('user', 'USER_ROLE');

INSERT INTO USERS VALUES('carmelo', '010101', true);
INSERT INTO AUTHORITIES VALUES('carmelo', 'ADMIN_ROLE');
INSERT INTO AUTHORITIES VALUES('carmelo', 'USER_ROLE');

Tenemos lo siguiente:

image

Para finalizar solo requerimos cambiar el proveedor de autenticación, lo hacemos en la clase WebSecConfig, de la siguiente manera:

@Configuration
@EnableWebSecurity
public class WebSecConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .jdbcAuthentication()
                .dataSource(dataSource);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
         // ...
    }
}

Si ejecutamos la aplicación y iniciamos sesión con las credenciales: carmelo y 010101, si entramos todo está correcto.

image

Usar una base de datos existente

En el caso de que tengamos una base de datos cuyos nombres no concuerden con el esquema utilizado por Spring Security tenemos la posibilidad de escribir las consultas SQL que se adapten a nuestra base de datos, de este modo integramos la seguridad a una BD propia.

Para nuestra demostración cambiaremos el nombre de alguna de las tablas y columnas de la BD, luego ingresaremos las correspondientes consultas SQL.

/*************** schema.sql *************************/

CREATE TABLE USUARIOS (
    NOMBRE VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    CORREO VARCHAR_IGNORECASE(50) NOT NULL
);

CREATE TABLE AUTHORITIES (
    NOMBRE VARCHAR_IGNORECASE(50) NOT NULL,
    AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS
    FOREIGN KEY(NOMBRE) REFERENCES USUARIOS(NOMBRE)
);


/*************** data.sql *************************/

INSERT INTO USUARIOS VALUES('user', '123', 'user@correo.com');
INSERT INTO AUTHORITIES VALUES('user', 'USER_ROLE');

INSERT INTO USUARIOS VALUES('carmelo', '010101', 'carmelo@tutor.com');
INSERT INTO AUTHORITIES VALUES('carmelo', 'ADMIN_ROLE');
INSERT INTO AUTHORITIES VALUES('carmelo', 'USER_ROLE');

La configuración de la seguridad para usar esta nueva base de datos es la siguiente:

@Configuration
@EnableWebSecurity
public class WebSecConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("SELECT nombre, password, true FROM usuarios WHERE nombre = ?")
                .authoritiesByUsernameQuery("SELECT nombre, authority FROM authorities WHERE nombre = ?");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
          // ...
    }
}

El método usersByUsernameQuery() define la consulta usada para obtener las credenciales del usuario, la misma debe devolver tres columnas, nombre, contraseña y el booleano que indica si el usuario está habilitado, en este ejemplo esta última columna siempre es true ya que la base de datos que diseñamos no almacena dicho valor.

Por medio de authoritiesByUsernameQuery() agregamos la consulta que obtiene los roles correspondientes al usuario, para este caso la consulta debe retornar dos columnas, una con el nombre y otra con los roles.

Descargar proyecto java: springsecurity-basedatos.zip

Comentarios

Entradas populares de este blog

Conectar SQL Server con Java

Entrenar OpenCV en Detección de Objetos

Detección de figuras geométricas

Procesamiento de imágenes en OpenCV

Conociendo la clase cv::Mat de OpenCV