lunes, 29 de marzo de 2021

Spring Boot: Seguridad básica

Este es el cuarto artículo de la serie sobre Spring Boot que comenzamos el mes de Febrero. Estos son los anteriores:

El proyecto con el que estamos trabajando esta en GitHub, esa es su URL:  https://github.com/tecnificados/boot

Hoy vamos a empezar a trabajar con Spring Security a nivel muy básico.

Los cambios que vamos a comentar se corresponden con algunos commits del 28 de Marzo de 2021:



Hasta ahora hemos tocado la seguridad muy de refilón, solo hemos dicho que las páginas que hemos ido creando no requerían ningún tipo de seguridad.

Ahora vamos a crear una página que sí requiera estar identificado, y para realizar esa autenticación vamos a crear una página simple de login.

Una de las características que trae de serie Spring Security es protección contra CSRF: entre otras cosas nos garantiza que nadie nos manipula el código de nuestras páginas cuando estamos trabajando con un formulario (algo muy fácil de hacer gracias a las herramientas de depuración de los navegadores).

A continuación vamos repasar los cambios mas importantes en los commits:

Usuario Tecnificado (Commit 69e7131)

Por defecto la configuración de usuarios se encuentra en memoria (más adelante lo cambiaremos para que utilice la base de datos para persistir esta información). En este commit lo que hacemos es añadir al usuario "Tecnificado" con password "iroman" a la lista de usuarios. Lo hacemos con estas líneas en la clase WebSecurityConfig:

@Bean
@Override
public UserDetailsService userDetailsService() {
	UserDetails user =
		 User.withDefaultPasswordEncoder()
			.username("Tecnificado")
			.password("ironman")
			.roles("USER")
			.build();

	return new InMemoryUserDetailsManager(user);
}

Página de login y Lógica (Commit 2615f63)

En este commit hemos hecho dos cambios importantes:

En el primero, configuramos que nuevas vistas vamos a añadir y sus URLs. Lo hacemos en la clase MvcConfig:

public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController("/hello").setViewName("hello");
	registry.addViewController("/login").setViewName("login");
}

En la aplicación ya está configurado que la página de login es "login", así que no hay que hacer nada.

Toda la lógica de la página de login, tampoco la necesitamos implementar, Spring sabe lo que tiene que hacer.

Y para acabar añadimos la JSP, una hoja de estilos y el logo.

Lo más importante de estos cambios es el "input hidden" del formulario, ya que sin esto Spring Security no permitirá identificarnos:

<form class="form-signin" action="<c:url value='/login' />" method="post">
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

Esto hay que añadirlo dentro de todos los formularios que se utilicen en la aplicación.

Página Hello y retoques (Commit 38fea25)

Aquí terminamos añadiendo el código de la página que requiere la autenticación: un HTML simple que recupera el nombre del usuario y nos lo muestra en pantalla.

También he añadido un botón desconectar (con formulario) que nos devuelve a la página de login.

En la página inicial (index) he añadido un enlace a la nueva pagina en la parte superior, si pulsamos en ella nos aparecerá la página de login para poder acceder:







En este punto ya tenemos nuestra aplicación trabajando con una seguridad básica. Ya podemos plantearnos empezar con las pantallas del CRUD y trabajar más en serio con la Base de Datos, aunque a lo mejor hacemos algún cambio antes de continuar.

Nos vemos pronto.

sábado, 13 de marzo de 2021

Spring Boot: Trabajando con bases de datos

Este es el tercer artículo de la serie sobre Spring Boot que comenzamos hace el mes pasado, los anteriores los tienes aquí:
El proyecto con el que estamos trabajando esta en GitHub en la siguiente URL:  https://github.com/tecnificados/boot

Hoy vamos a empezar a trabajar con bases de datos, la configuración ya la establecimos en el anterior post, así que hoy vamos empezar a utilizar esa conexión.

Los cambios que vamos a comentar se corresponden con los commits del 12 de Marzo de 2021:



Clases de acceso a datos, modelo, servicio y script de BBDD (Commit 92a6848)

El objetivo de hoy es crear un tabla y trabajar con ella (insertando, borrando,...), así que lo primero que tenemos que hacer es crear esa tabla a través de un script, lo tenéis en el fichero 'incidencia.sql' en la carpeta 'scripts':

CREATE TABLE incidencia ( 
 id int(11) NOT NULL, 
 titulo varchar(255) DEFAULT NULL, 
 descripcion varchar(2000) DEFAULT NULL, 
 estado int(20) NOT NULL, 
 autor varchar(50) NOT NULL ); 

ALTER TABLE incidencia ADD PRIMARY KEY (id);

Ahora vamos a crear las clases necesarias para trabajar con esa tabla, gracias al framework que utilizamos la cantidad de código que utilizamos es muy pequeña, pero no os asustéis que funciona. Estas son las clases que necesitamos:


Empezamos por el modelo, la clase 'Incidencia.java' es la clase que se corresponde con nuestra tabla. A nivel de código no hay mucho misterio, tiene los atributos que se corresponden con nuestras columnas, y los métodos get y set, un bean de los de toda la vida, pero con anotaciones:

@Entity
public class Incidencia {

@Id
private Long id;

@Column
private String titulo;

@Column
private String descripcion;

@Column
private Integer estado;

@Column
private String autor;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getTitulo() {
return titulo;
}

public void setTitulo(String titulo) {
this.titulo = titulo;
}

public String getDescripcion() {
return descripcion;
}

public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}

public Integer getEstado() {
return estado;
}

public void setEstado(Integer estado) {
this.estado = estado;
}

public String getAutor() {
return autor;
}

public void setAutor(String autor) {
this.autor = autor;
}

}


Ahora vamos a ver la clase DAO (Data Access Object), esta es la clase que encarga de realizar las comunicaciones directamente con la Base de Datos.

public interface IncidenciaDAO extends CrudRepository<Incidencia, Long> {

@Query(value = "SELECT coalesce(max(id), 0) FROM Incidencia")
Long getMaxTransactionId();

}

Como podéis ver no tiene casi código ya que a través de la clase que hereda ya obtiene las operaciones básicas.

He añadido el método "getMaxTransactionId()" que se corresponde con la consulta que se puede leer. Lo que hace es sacar el id más alto de la tabla, en caso de no existir devuelve 0.

Ahora tenemos que ver las clases que están dentro del paquete "service". Estas clases son las encargadas de trabajar con las DAO. La verdad es que no tienen mucho que explicar, os recomiendo que las veáis siguiendo este orden:
  1. GenericService.java: interfaz genérica (que nos sirve para cualquier tabla).
  2. GenericServiceImp.java: implementación de la anterior.
  3. IncidenciaService.java: interfaz para Incidencia (aquí nos definimos las particularidades de nuestra clase).
  4. IncidenciaServiceImpl.java: implementación.
Ya tenemos las clases listas para operar con nuestra tabla.

Configuración del pool de conexiones (Commit 1b9d186)

Este es un cambio rápido: vamos a activar el pool de conexiones. 

Necesitamos configurarlo para que haya más de una conexión disponible, de manera que si tenemos más de un usuario, haya conexiones para todos y no se ralentice nuestra aplicación en caso de llevar parada mucho tiempo y que de repente aparezcan varios usuarios a la vez.

Se configura muy fácilmente añadiendo estas líneas en nuestro 'application.properties':

#Configuración del Pool de conexiones
spring.datasource.initial-size=10
spring.datasource.max-active=20
spring.datasource.max-idle=5
spring.datasource.min-idle=1

Cambios en el controlador que utilizan la BBDD (Commit e55a9aa)

En este cambio lo que hemos hecho es cambiar nuestro controlador inicial con nuevos métodos y modificando el inicial para trabajar con BBDD.

El cambio fundamental son estas líneas:

@Autowired
private IncidenciaService incidenciaService;

Gracias a ellas se inyecta el servicio que nos permite trabajar con nuestra tabla.

El método inicial ahora se llama index(), y hace una llamada al listado de incidencias, pasando el resultado al modelo:

        @RequestMapping(DEFAULT)
public ModelAndView  index() {

List<Incidencia> list = incidenciaService.getAll();
ModelAndView model = new ModelAndView();
model.addObject("list", list);
model.setViewName("index");

return model;
}

DEFAULT es una constante que se corresponde con "/" que es la página por defecto: localhost:8080

He añadido el método add() que añade una incidencia (siempre la misma) cada vez que se le invoca:

        @RequestMapping(ADD)
@ResponseBody
public String  add() {
Long id=incidenciaService.maxId()+1;
Incidencia incidencia = new Incidencia();
incidencia.setId(id);
incidencia.setAutor("Juan Carlos");
incidencia.setTitulo("Incidencia "+incidencia.getId());
incidencia.setDescripcion("La aplicación no arranca");
incidencia.setEstado(0);
incidenciaService.save(incidencia);

return "added";
}
ADD es una constante que se corresponde con "/add": localhost:8080/add

Con @ResponseBody indicamos que lo que se devuelve y debe aparecer directamente en la salida html.

El método empty() lo que hace es borrar todas incidencias creadas, siguiendo la misma lógica.

Para que todo funcione hay que añadir las nuevas operaciones en nuestro 'WebSecurityConfig' e indicarle que no requieren autenticación:

         .antMatchers(StartController.DEFAULT,
StartController.ADD,
StartController.EMPTY,
"/login",
/resources/**").permitAll()
.anyRequest().authenticated()


Cambios en la página inicial para mostrar la información de Incidencias (Commit 8a10cf3)

En este commit solo hemos modificado la JSP para que en caso de existir incidencias en la tabla las muestre en la página inicial, en caso contrario nos indicará que no hay incidencias:




Ahora con logs (Commit f342578)

Y para terminar, he añadido un sistema de log en la aplicación, realmente ya estaba, solo lo he añadido como atributo estático en el controlador:

    Logger logger = LoggerFactory.getLogger(StartController.class);

Y un fichero de configuración (logback-spring.xml) para elegir, si consola, fichero o ambos. 

En este punto ya tenemos nuestra aplicación trabajando con BBDD, ahora lo ideal sería generar un CRUD con diferentes pantallas y securizando el acceso a través de una página de login. Veremos como hacerlo próximamente.

Nos vemos pronto.