Menu

Spring Boot Security | Login | Inicio de Sesion + MYSQL #1

0 Comment


Spring Boot Security Inicio de Sesión con MYSQL

Crearemos un inicio de sesión sencillo pero bonito como lo hicimos aqui, con un usuario con role “USER” y otro usuario “ADMIN”.

Para empezar deberás de saber como conectar tu aplicación con la base de datos MYSQL. Si no sabes como, puedes mirar mi tutorial donde lo explico. spring-boot-mysql.

Version Video.

En caso de que quieras ver el video donde programo este tutorial paso a paso aqui te lo dejo. sino solo sigue bajando.

Paso a Paso

Configurar Vista

  1. Crear un proyecto Spring Boot, con las respectivas dependencias.
    1. Web
    2. Thymeleaf
    3. Devtools
    4. JPA
    5. MYSQL
      Proyecto Login Dependencias

      Proyecto Login Dependencias

  2. Crear las paginas HTML para el inicio de sesión y el acceso por roles. El diseño de la pagina lo podrán ver Aqui, y el código lo pueden descargar de Aqui. Asegúrate de tener la estructura de archivos de la siguiente manera.
    Proyecto Login Estructura

    Proyecto Login Estructura

  3. menu.html: Codigo Fuente
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
            <title>Hello World!</title>
        </head>
        <body>
            <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
            <form th:action="@{/logout}" method="post">
                <input type="submit" value="Cerrar Sesion"/>
            </form>
            <h1><a th:href="@{/admin}"> admin</a> | <a th:href="@{/user}"> user</a></h1>
        </body>
    </html>

    admin.html

    <h1>Admin Yeah</h1>

    user.html

    <h1>User Yeah!</h1>
  4. Configura la conexión a la base de datos en el archivo application.properties: Codigo Fuente
    Proyecto Login Application Properties

    Proyecto Login Application Properties

  5. Crear el controlador que redirigirá los request http (En index.html asegúrate que en el form el method sea get y el action sea “/menu”):
    <form class="col-12" th:action="@{/menu}" method="get">

    Codigo Fuente

    Proyecto Login Controller

    Proyecto Login Controller

  6. Inicia la aplicación con http://localhost:8080/ y deberás de tener algo parecido a la primer imagen y cuando presiones el botón “Ingresar” tengas la segunda imagen. Si tienes problemas visualizando las imágenes o el estilo css, verifica que la imagen y el link del css tenga el tag th apropiadamente.
    Proyecto Login Index

    Proyecto Login Index


    Proyecto Login Map Site

    Proyecto Login Map Site

Asegurar la aplicación

  1. Agrega la dependencia de Spring Boot Security a tu pom.xml: Codigo Fuente
    <!-- Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- *** -->
    1. El paso anterior habilita el componente de seguridad, pero hay que configurarlo. Crear la siguiente clase que hereda de WebSecurityConfigurerAdapter. Codigo Fuente
      package com.cristianruizblog.loginSecurity.config;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
      import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      
      import com.cristianruizblog.loginSecurity.service.UserDetailsServiceImpl;
      
      @Configuration
      @EnableWebSecurity
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
      
          //Necesario para evitar que la seguridad se aplique a los resources
          //Como los css, imagenes y javascripts
          String[] resources = new String[]{
                  "/include/**","/css/**","/icons/**","/img/**","/js/**","/layer/**"
          };
      	
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                  .authorizeRequests()
      	        .antMatchers(resources).permitAll()  
      	        .antMatchers("/","/index").permitAll()
      	        .antMatchers("/admin*").access("hasRole('ADMIN')")
      	        .antMatchers("/user*").access("hasRole('USER') or hasRole('ADMIN')")
                      .anyRequest().authenticated()
                      .and()
                  .formLogin()
                      .loginPage("/login")
                      .permitAll()
                      .defaultSuccessUrl("/menu")
                      .failureUrl("/login?error=true")
                      .usernameParameter("username")
                      .passwordParameter("password")
                      .and()
                  .logout()
                      .permitAll()
                      .logoutSuccessUrl("/login?logout");
          }
          BCryptPasswordEncoder bCryptPasswordEncoder;
          //Crea el encriptador de contraseñas	
          @Bean
          public BCryptPasswordEncoder passwordEncoder() {
      		bCryptPasswordEncoder = new BCryptPasswordEncoder(4);
      //El numero 4 representa que tan fuerte quieres la encriptacion.
      //Se puede en un rango entre 4 y 31. 
      //Si no pones un numero el programa utilizara uno aleatoriamente cada vez
      //que inicies la aplicacion, por lo cual tus contrasenas encriptadas no funcionaran bien
              return bCryptPasswordEncoder;
          }
      	
          @Autowired
          UserDetailsServiceImpl userDetailsService;
      	
          //Registra el service para usuarios y el encriptador de contrasena
          @Autowired
          public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
       
              // Setting Service to find User in the database.
              // And Setting PassswordEncoder
              auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());     
          }
      }
      Java

      Primero que todo cuando copies esta clase te saldrá un error con el objeto UserDetailsServiceImpl el cual crearemos después de esta explicación.

      Como segundo te explicare la configuración implementada en el metodo configure:

    2. El metodo authorizeRequests() permite restringir y/o dar acceso request HTTP 
      1. antMatchers(): Lista de URL que corresponden a un RequestMapping como lo hacemos en los controladores.
      2. permitAll(): Especifica que estas URLs son accesibles por cualquiera.
      3. access(): permite el acceso cumpliendo la expresión, en este caso tenemos la expresion “hasRole()”. Donde verifica si el usuario tiene ese especifico Role.
      4. anyRequest(): Ya que la configuración es lineal poniendo este metodo al final interpreta los request a las URLs que no fueron descritos, y en conjunto con el metodo authenticated() permite y da acceso a cualquier usuario que este autenticado.
    3. El metodo fromLogin(). Permite personalizar el proceso de inicio de sesión
      1. loginPage(): indica la url de la pagina de inicio de sesión
      2. defaultSuccessUrl(): Indica a cual URL sera redirigido cuando el usuario inicie sesión.
      3. failureUrl(): Indica a cual URL sera redirigido cuando el inicio de sesión falla.
      4. usernameParameter()passwordParameter(): Indica el nombre de los parámetros respectivamente.
    4. El metodo logout(): Personaliza el proceso de cierre de sesión.
      1. logoutSuccessUrl(): Indica la URL donde sera redirigido cuando el usuario cierre sesión.

Capa de servicio y repositorio

  1. Empezaremos creando la siguiente estructura de archivos:
    Proyecto Login Estructura Completa

    Proyecto Login Estructura Completa

  2. Entidades | Entity: Siguiendo los lineamientos especificados por la documentación de Spring 5.2.0 Se necesitan dos tablas, una para Usuarios y otra para los Roles (Conocido como Authorities).Documentacion Oficial. Y esta es la implementacion con Spring JPA e Hibernate.
      1. Authority: Codigo Fuente
        package com.cristianruizblog.loginSecurity.entity;
        
        import javax.persistence.Column;
        import javax.persistence.Entity;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        
        @Entity
        public class Authority {
        
        	@Id
        	@GeneratedValue(strategy=GenerationType.AUTO)
        	private Long id;
        	
        	@Column
        	private String authority;
        	
        	public String getAuthority() {
        		return authority;
        	}
        
        	public void setAuthority(String authority) {
        		this.authority = authority;
        	}
        }
        Java

         

      2. User: Codigo Fuente
        package com.cristianruizblog.loginSecurity.entity;
        
        import java.util.Set;
        import javax.persistence.Column;
        import javax.persistence.Entity;
        import javax.persistence.FetchType;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        import javax.persistence.JoinColumn;
        import javax.persistence.JoinTable;
        import javax.persistence.ManyToMany;
        
        import com.cristianruizblog.loginSecurity.entity.Authority;
        
        @Entity
        public class User {
        
        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        private Long id;
        
        @Column
        private String username;
        
        @Column
        private String password;
        
        @Column
        private boolean enabled;
        
        @ManyToMany(fetch = FetchType.EAGER)
        @JoinTable(name="authorities_users",
        joinColumns=@JoinColumn(name="usuario_id"),
        inverseJoinColumns=@JoinColumn(name="authority_id"))
        private Set<Authority> authority;
        
        //Getters y Setters
        
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id == null) ? 0 :id.hashCode());
            return result;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            User other = (User) obj;
            if (id == null) {
                if (other.id != null)
                    return false;
                } else if (!id.equals(other.id))
                    return false;
                return true;
        }
        
        @Override
        public String toString() {
            return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
        }
        
        }
        Java
  3. Repositorios | Acceso a Datos: Por el momento tenemos una sola interface que hereda de CrudRepository y donde declaramos un metodo donde se busca por username. Si necesitas un poco mas de explicación acerca de esto puedes ver estos tutoriales. JPA Parte 1 y JPA Parte 2. Codigo Fuente
    package com.cristianruizblog.loginSecurity.repository;
    
    import java.util.Optional;
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    import com.cristianruizblog.loginSecurity.entity.User;
    
    @Repository
    public interface UserRepository extends CrudRepository<User, Long>  {
        public Optional<User> findByUsername(String username);
    }
    Java
  4. Service: Necesitaremos crear la implementacion para la interface UserDetailsService, la cual esta dentro de la dependencia de Spring Security Codigo Fuente
package com.cristianruizblog.loginSecurity.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 com.cristianruizblog.loginSecurity.entity.Authority;
import com.cristianruizblog.loginSecurity.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;
	
    @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
     //Buscar el usuario con el repositorio y si no existe lanzar una exepcion
     com.cristianruizblog.loginSecurity.entity.User appUser = 
                 userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("No existe usuario"));
		
    //Mapear nuestra lista de Authority con la de spring security 
    List grantList = new ArrayList();
    for (Authority authority: appUser.getAuthority()) {
        // ROLE_USER, ROLE_ADMIN,..
        GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getAuthority());
            grantList.add(grantedAuthority);
    }
		
    //Crear El objeto UserDetails que va a ir en sesion y retornarlo.
    UserDetails user = (UserDetails) new User(appUser.getUsername(), appUser.getPassword(), grantList);
         return user;
    }
}
Java

Crear usuarios.

Necesitaremos insertar datos en las tablas que tenemos en la base de datos. Empezaremos por crear los usuarios y las autoridades y luego asignaremos los cruzaremos.

Usuarios: Para poder crear un usuario necesitaremos la contraseña encriptada por lo cual crearemos la siguiente clase para simular el encriptador. Codigo Fuente

package com.cristianruizblog.loginSecurity.util;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class Passgenerator {

    public static void main(String ...args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(4);
        //El String que mandamos al metodo encode es el password que queremos encriptar.
	System.out.println(bCryptPasswordEncoder.encode("1234"));
    }
}
Java

Esta clase deberá de ejecutarse como aplicación Java, y la contraseña encriptada aparecerá en tu consola.

Ahora crearemos un usuario admin y user

Reemplaza la palabra password por lo que apareció en tu consola en el paso anterior.

INSERT INTO user (id,enabled,password,username) 
VALUES(1,0b1,"password","admin");

INSERT INTO user (id,enabled,password,username) 
VALUES(2,0b1,"password","user");

INSERT INTO authority (id,authority) VALUES (1,"ROLE_ADMIN");
INSERT INTO authority (id,authority) VALUES (2,"ROLE_USER");
INSERT INTO authorities_users (usuario_id, authority_id) VALUES (1,1);
INSERT INTO authorities_users (usuario_id, authority_id) VALUES (1,2);
INSERT INTO authorities_users (usuario_id, authority_id) VALUES (2,2);
SQL

PRECAUCION:

Hay que cambiar el index.html un poco. El action hay que apuntarlo a /login y el metodo debe ser post.

<form class="col-12" th:action="@{/login}" method="post">

Ya solo falta ejecutar la aplicación con ambos usuarios y verificar que con el usuario user no se puede acceder al link de admin

Bueno esto fue todo, no dudes en dejar tu comentario ademas puedes ver mi codigo completo en mi repositorio de github o ver este ejemplo en video. Muchas gracias por llegar al final del tutorial.

GitHub: https://github.com/cruizg93/SpringBoot-Security-MySql

@Cruizg93

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *