🔐 Login con Spring Security + MVC + MySQL + Thymeleaf + Bootstrap
🧩 Guía COMPLETA y EXPLICADA paso a paso (nivel principiante → intermedio)
En este tutorial no solo vas a copiar código: vas a entender cómo funciona cada pieza de un sistema de autenticación real con Spring Boot.
🧠 1. ¿Qué estamos construyendo?
Vamos a crear un sistema que:
Permite iniciar sesión con usuario y contraseña
Valida credenciales contra MySQL
Protege rutas según roles (ADMIN / USER)
Usa vistas con Thymeleaf
Tiene diseño con Bootstrap
👉 Todo esto usando el patrón MVC (Modelo - Vista - Controlador).
🏗️ 2. Crear el proyecto
Puedes usar Spring Initializr o NetBeans.
Selecciona:
Spring Web → para controladores y MVC
Spring Security → autenticación
Spring Data JPA → acceso a base de datos
Thymeleaf → vistas HTML dinámicas
MySQL Driver → conexión a BD
📦 3. Dependencias (¿por qué son importantes?)
<dependencies>
<!-- Permite crear controladores y endpoints web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Motor de plantillas HTML -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Seguridad: login, roles, filtros -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- ORM: convierte tablas en objetos Java -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Conexión a MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
</dependencies>
💡 Idea clave:
Spring Boot trabaja por módulos. Cada dependencia activa funcionalidades automáticamente (auto-configuración).
🗄️ 4. Base de datos (modelo real)
CREATE DATABASE login_db;
USE login_db;
CREATE TABLE roles (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(50)
);
CREATE TABLE usuarios (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password VARCHAR(255),
enabled BOOLEAN
);
CREATE TABLE usuario_roles (
usuario_id INT,
rol_id INT,
FOREIGN KEY (usuario_id) REFERENCES usuarios(id),
FOREIGN KEY (rol_id) REFERENCES roles(id)
);
🔍 Explicación
usuarios→ almacena credencialesroles→ define permisos (ADMIN, USER)usuario_roles→ relación muchos a muchos
💡 Esto permite que:
Un usuario tenga varios roles
Un rol pertenezca a varios usuarios
⚙️ 5. application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/login_db
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.thymeleaf.cache=false
🔍 Explicación
ddl-auto=update→ crea/actualiza tablas automáticamenteshow-sql=true→ muestra consultas en consolacache=false→ útil en desarrollo (recarga HTML sin reiniciar)
📁 6. Estructura del proyecto (MVC)
com.tuapp
│
├── controller → recibe peticiones HTTP
├── service → lógica de negocio
├── repository → acceso a datos
├── model → entidades (tablas)
├── security → configuración de seguridad
└── config → configuración general
💡 Clave: Separar responsabilidades evita código desordenado.
🧩 7. Entidades (JPA)
👤 Usuario
@Entity
@Table(name = "usuarios")
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private boolean enabled;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "usuario_roles",
joinColumns = @JoinColumn(name = "usuario_id"),
inverseJoinColumns = @JoinColumn(name = "rol_id")
)
private List<Rol> roles;
}
🔍 Explicación
@Entity→ convierte la clase en tabla@ManyToMany→ relación con rolesEAGER→ carga roles automáticamente (importante para seguridad)
🔑 Rol
@Entity
@Table(name = "roles")
public class Rol {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
}
🗃️ 8. Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
Optional<Usuario> findByUsername(String username);
}
🔍 Explicación
Spring Data JPA genera automáticamente:
SELECT
INSERT
UPDATE
DELETE
👉 findByUsername se crea automáticamente por convención.
🔐 9. Servicio de autenticación (CLAVE)
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UsuarioRepository repo;
public UserDetailsServiceImpl(UsuarioRepository repo) {
this.repo = repo;
}
@Override
public UserDetails loadUserByUsername(String username) {
Usuario user = repo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));
List<GrantedAuthority> roles = user.getRoles()
.stream()
.map(r -> new SimpleGrantedAuthority(r.getNombre()))
.toList();
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true, true, true,
roles
);
}
}
🔍 Explicación
Spring Security llama automáticamente este método:
👉 loadUserByUsername
Lo que hace:
Busca el usuario en BD
Convierte roles a permisos (
GrantedAuthority)Devuelve un objeto que Spring Security entiende
💡 Aquí ocurre la magia del login.
🔧 10. Configuración de seguridad
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/css/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
🔍 Explicación DETALLADA
🔒 authorizeHttpRequests
Define quién puede entrar:
/login→ público/admin/**→ solo ADMIN/user/**→ USER o ADMIN
🔑 formLogin
Página personalizada de login
Redirección al iniciar sesión
🔓 logout
Cierra sesión
Redirige al login
🔐 PasswordEncoder
Encripta contraseñas
Usa BCrypt (seguro)
🎮 11. Controlador
@Controller
public class AuthController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/home")
public String home() {
return "home";
}
}
🔍 Explicación
/login→ muestra formulario/home→ página protegida
🎨 12. Vista Login
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container d-flex justify-content-center align-items-center" style="height:100vh;">
<div class="card p-4 shadow" style="width:350px;">
<h3 class="text-center">Iniciar Sesión</h3>
<form th:action="@{/login}" method="post">
<input type="text" name="username" class="form-control mb-2" placeholder="Usuario" required>
<input type="password" name="password" class="form-control mb-3" placeholder="Contraseña" required>
<button class="btn btn-primary w-100">Entrar</button>
</form>
<div th:if="${param.error}" class="text-danger mt-2">
Credenciales inválidas
</div>
<div th:if="${param.logout}" class="text-success mt-2">
Sesión cerrada
</div>
</div>
</div>
</body>
</html>
🔍 Explicación
th:action="@{/login}"→ Spring maneja el POST automáticamente${param.error}→ muestra error si login fallaBootstrap → diseño moderno sin CSS extra
🔑 13. Crear usuario inicial
@Bean
CommandLineRunner init(UsuarioRepository repo, PasswordEncoder encoder) {
return args -> {
Usuario user = new Usuario();
user.setUsername("admin");
user.setPassword(encoder.encode("1234"));
user.setEnabled(true);
Rol rol = new Rol();
rol.setNombre("ROLE_ADMIN");
user.setRoles(List.of(rol));
repo.save(user);
};
}
🔍 Explicación
Se ejecuta al iniciar la app
Inserta usuario automáticamente
IMPORTANTE: contraseña encriptada
🚀 14. Flujo completo del login (MUY IMPORTANTE)
Usuario abre
/loginIngresa credenciales
Spring Security intercepta el POST
Llama a
UserDetailsServiceValida contraseña
Si es correcto → redirige a
/homeSi falla → vuelve a login con error
✅ Resultado final
✔ Sistema de login profesional
✔ Seguridad real con Spring Security
✔ Roles y permisos
✔ Interfaz moderna
✔ Código limpio y escalable
💡 Próximos pasos recomendados
Registro de usuarios
CRUD de usuarios
Panel admin
JWT para API REST
Protección CSRF avanzada
🧠 Conclusión
Este proyecto ya es una base sólida para:
Sistemas empresariales
APIs seguras
Aplicaciones reales
Comentarios
Publicar un comentario