Seguridad WEB en Spring Boot

Hola de nuevo, estudiantes 😉 . En esta entrada voy a explicar  como gestiona Spring la seguridad. No todo, por supuesto, que el tema de la seguridad daría para un libro muy gordote, pero al menos aprender a securizar una pagina web. En una próxima entrada hablare de como securizar un servicio REST.

Como siempre, comienzo diciendo que el código fuente de lo que explico lo tenéis en mi pagina de GITHUB, en https://github.com/chuchip/OAuthServer. El programa esta realizado en Java, usando Spring Boot.

Bien, empecemos por como securizar una pagina web en Spring.

Realmente usando Spring Boot, es muy sencillo, pues haremos uso de lo que Spring denomina starters, que no son sino grupos de paquetes agrupados por las funcionalidades que ofrecen. Asi en este caso, incluiremos el paquete Web, Thymeleaf y, por supuesto, Security.

Aquí tenéis un pantallazo de Eclipse seleccionando los paquetes necesarios.

De todos modos,  ya sabéis que en el fichero pom.xml podéis ver las dependencias más detalladamente.

Thymeleaf, por si alguien no lo existe es un software que se integra perfectamente con Spring y que permite realizar plantillas de páginas WEB.  Como JSP, pero muy mejorado o si lo preferís un JavaServer Faces si conocéis más el mundo JavaEE. El caso es que permite realizar paginas HTML que se integran perfectamente con nuestras clases desarrolladas con Spring.

Como queremos poder ver en nuestra página web el nombre del usuario con el que nos hemos registrado, debemos usar la libreria de seguridad de Thymeleaf para Spring. Para ello incluiremos las siguientes lineas en nuestra pagina web.

  <groupId>org.thymeleaf.extras</groupId>
  		 <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    		<version>3.0.3.RELEASE</version>
</dependency>

Y aquí podéis ver como quedara la estructura de nuestro programa

Ahora empecemos a declarar nuestra primera clase, a la que he llamado WebSecurityConfiguration.java

@SpringBootApplication
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

	public static void main(String[] args) {
		SpringApplication.run(WebSecurityConfiguration.class, args);
	}
	
	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
	       return super.authenticationManagerBean();
	}
	
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
    	
    	UserDetails user=User.builder().username("user").password(passwordEncoder().encode("secret")).
    			roles("USER").build();
    	UserDetails userAdmin=User.builder().username("admin").password(passwordEncoder().encode("secret")).
    			roles("ADMIN").build();
        return new InMemoryUserDetailsManager(user,userAdmin);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
  
   @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        	.csrf().disable()
               .authorizeRequests()
            	.antMatchers("/","/index","/webpublico").permitAll()
            	.antMatchers("/webprivado").authenticated()
            	.antMatchers("/webadmin").hasRole("ADMIN").and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout() // Metodo get pues he desabilitado CSRF
                .permitAll();
    }
}

Lo primero es poner las etiquetas @SpringBootApplication y @EnableWebSecurity. La primera etiqueta es obvia, ya que nuestra aplicación queremos que funcione con Spring Boot ;-). Vamos, que o la ponéis o no seria una aplicacion Spring Boot y ya nos podemos ir a casa :-D. La segunda es para especificar que queremos que se active la seguridad Web, realmente esta etiqueta no es obligatorio ponerla porque Spring Boot que es muy listo, en cuanto ve que tenemos el paquete security (en el pom.xml, recordad) en nuestro proyecto la incluye, pero no es mala cosa ponerla por claridad, aunque sea redundante.

Ahora especificamos que nuestra clase va a heredar de WebSecurityConfigurerAdapter pues vamos a sobrescribir algunas de las funciones de esa clase. Para que lo entendáis, básicamente Spring mira a ver si hay alguna clase que implemente el interface WebSecurityConfigurer, el cual implementa la clase WebSecurityConfigurerAdapter , y si lo hay pues utiliza las funciones que tiene ese interface para configurar la seguridad de la aplicación.

Si no tuviéramos una clase que implementara ese interface, Spring simplemente no dejaría acceder a ninguna pagina de nuestra aplicación, lo que, como comprenderéis no es muy practico 😉

Bien, ahora sobrescribimos la función authenticationManagerBean que devolverá la clase encargada de manejar las autentificaciones (como su propio nombre indica 😉 ). Vale, os estaréis preguntando, ¿ pero si solo llama a la función padre para que la definimos?. Muy simple porque le ponemos la etiqueta @Bean, para que Spring sepa de donde sacar (inyectar) un objeto tipo AuthenticationManager pues lo necesita para controlar la seguridad.

En la función userDetailsService definimos los usuarios que van a tener acceso a nuestra web. En este caso creamos dos usuarios: user y admin (sí lo se, no es que me haya currado mucho el tema de los nombres 😉 ). Cada uno de ellos con su contraseña y su ROL. Aclarar que el ROL es un literal libre, es decir que ahi podemos poner lo que queramos, por ejemplo USUARIO_CON_PECAS. El caso es que luego ese ROL lo utilizaremos y debe coincidir letra a letra con el establecido.

Observar también que la contraseña se la damos encriptada, en este caso con el algoritmo BCrypt. Esto lo hacemos llamando a la función passwordEncoder, la cual esta anotada con la etiqueta @Bean para que Spring la use.

Es decir, Spring necesita saber que sistema de encriptación estamos usando para guardar nuestras contraseñas, y para ello busca un objeto que implemente el interface PasswordEncoder. Si no lo encuentra nos fallara la aplicación.

Aclarar que estamos usando la forma más sencilla de declarar los usuarios, guardándolos en memoria con la clase InMemoryUserDetailsManager. En un programa de verdad, se usaría JdbcUserDetailsManager que nos permitiría guardarlos en una base de datos o cualquier otra clase que implemente el interface UserDetailsManager como podría ser  LdapUserDetailsManager si quisiéramos usar un servicio LDAP.

Y ya solo nos falta definir que partes de nuestra aplicación vamos a proteger y que roles deben de tener permisos para acceder a cada parte de ella. Sí, he escrito roles y no usuarios porque como hemos dicho antes, al definir un usuario, lo debemos asignar a un role (o grupo que es más español, si lo preferís). Y, normalmente, las reglas de filtrado se aplican por el grupo al que pertenece el usuario. Para definir los permisos de cada recurso lo haremos configurando el objeto HttpSecurity recibido en la función protected void configure(HttpSecurity http)

Voy explicando linea a linea lo que se hace en esta función:

  • csrf().disable()

Deshabilita el control de csrf. CRSF son las siglas de Cross-site request forgery que como explica la Wikipedia

CRSF del inglés Cross-site request forgery o falsificación de petición en sitios cruzados) es un tipo de exploit malicioso de un sitio web en el que comandos no autorizados son transmitidos por un usuario en el cual el sitio web confía. Esta vulnerabilidad es conocida también por otros nombres como XSRF, enlace hostil, ataque de un click, cabalgamiento de sesión, y ataque automático.

El deshabilitar el CRSF tiene como efecto secundario que se pueda realizar un logout de una sesión con una petición HTTP tipo GET, pues por defecto solo se puede hacer con una petición POST.

 

  • .authorizeRequests()
    .antMatchers(“/”,”/index”,”/webpublico”).permitAll()

Especificamos que las peticiones que en la ruta este cualquiera de las cadenas “/”,”/index”,”/webpublico” no tendrán seguridad. Es decir estaran permitidas para todo el mundo.

  • antMatchers(“/webprivado”).authenticated()

Especificamos que las peticiones a la ruta “/webprivado” solo podran ser procesadas si el usuario esta autentificado, sin especificar a que ROL debe pertenecer.

  • .antMatchers(“/webadmin”).hasRole(“ADMIN”)

Solo los usuarios que sean del grupo ADMIN tendrán acceso a la URL “/webadmin”

La función antMatchers permite el uso de expresiones regulares, por lo que si, por ejemplo, quisiéramos aplicar una regla a todo lo que dependa de una ruta, podríamos poner esto :

http.antMatchers(“/users/**”).hasRole(“USER”) para especificar que cualquier petición a la URL /users/Y_LO_QUE_SEA solo tendrán acceso los usuarios  que pertenezcan al grupo USER.

  • .formLogin().loginPage(“/login”).permitAll()

Especificamos que la pagina de login sera “/login” y quede ser permitido llegar a todo el mundo.

  • logout().permitAll()

Especificamos que la pagina de desconexión debe ser permitido llegar a todo el mundo. Por defecto esta pagina responde en la URL “/logout”

Perfecto, ya tenemos definida la seguridad de nuestras paginas web, ahora solo queda definir los puntos de entrada a las páginas. Eso se hace en la clase WebController.java

@Controller
public class WebController {
	
	 @RequestMapping({"/","index"})
	  public String inicio() {
	    return "index";
	  }

	 @RequestMapping("/webprivado")
	  public String privado() {
	    return "privado";
	  }
	 @RequestMapping("/webpublico")
	  public String loginpub() {
	    return "publico";
	  }
	 @RequestMapping("/webadmin")
	  public String admin() {
	    return "admin";
	  }
	 @RequestMapping("/login")
	  public String login() {
	    return "login";
	  }
}

La clase como se ve no tiene muchos misterios,simplemente especificamos con la etiqueta @Controller que sera una clase donde vamos a definir puntos de entrada para las peticiones web.

En las diferentes funciones tenemos la etiqueta @RequestMapping  para especificar la URL que debe procesar cada función. Así la función inicio sera llamada cuando haya una petición a la URL “/” o “/index”. Observar que no hay que poner la barra inicial.

La cadena devuelta sera la plantilla Thymeleaf devuelta, de tal manera que la llamada  función inicio  devolverá la plantilla “index.html” que es la siguiente:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
    <head>
        <title>Página Inicio</title>
    </head>
    <body>
   		<h1>Pagina Inicio</h1>
   		<p>Pulsa  <a th:href="@{/webpublico}">aqui</a> para ver una pagina publica.</p>
   		<p>Si eres un usuario normal pulsa <a th:href="@{/webprivado}">aqui</a>  para ver una pagina privada</p>
   		<p>Si eres un administrador normal pulsa <a th:href="@{/webadmin}">aqui</a>  para ver la pagina del administrador</p>   		
   		<div sec:authorize="isAuthenticated()">
			Hello <span sec:authentication="name">someone</span>
			<p><a th:href="@{/logout}">Desconectar</a></p>
		</div>
   		
    </body>
</html>

¿A que casi parece HTML puro?. Es una de las ventajas de Thymeleaf  que usa  etiquetas HTML estandard. No voy a explicar este lenguaje, pero os explicare un poco las etiquetas usadas:

<a th:href=”@{/webpublico}”> . Crea un enlace a la URL “/webpublico”. Seria como poner la etiqueta “<A href=”/webpublico”>

<div sec:authorize=”isAuthenticated()”> Solo se renderizara el código en el DIV si el usuario esta autentificado. En otras palabras si el usuario no esta logueado no se mostrara en la página web lo que hay entre las etiquetas  DIV (de hecho no se mostrara ni el DIV).

<span sec:authentication=”name”>someone</span> Si el usuario esta autentificado mostrara el nombre del usuario, en caso contrario mostrara lo que haya entre las etiquetas span. En este caso mostraría someone.

Y con esto ya tenemos una aplicación securizada. !Sí!, con solo dos clases java y sus correspondientes ficheros HTML.

Para terminar esta entrada, os dejo unos capturas de pantalla de la aplicación:

 

Pagina de inicio sin loguear
Pagina de inicio sin haberse registrado
Pagina publica
Pagina inicio una vez identificado el usuario ‘user’
Identificandose con el usuario “user”

 
Error al acceder a pagina web “/admin” estando registrado con usuario ‘user’ que no tiene permisos para esa pagina web.

¡¡ Hasta otra !!

1 comentario en “Seguridad WEB en Spring Boot

Deja un comentario

English English Spanish Spanish