Tu código, tu control: aprende autenticación en Java. ¡Haz clic aquí!

Crearás una API REST en Spring Boot que permita:

  • Registrar usuarios (con contraseña encriptada).
  • Autenticar usuarios (login) y recibir un JWT.
  • Proteger endpoints con el token JWT.

Usaremos:

  • Spring Boot (Web, Security, Data JPA)
  • BCrypt para encriptar contraseñas
  • jjwt para tokens JWT
  • H2 como base de datos en memoria para el ejemplo

1. Prerrequisitos

  • Java 17+
  • Maven
  • Conocimientos básicos de Spring Boot

2. Dependencias pom.xml (fragmento)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

3. application.properties (mínimo para demo)

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

# JWT (llave de ejemplo; en producción usa variable de entorno y algo más seguro)
jwt.secret=mi-secreto-muy-largo-y-seguro
jwt.expiration=3600000  # 1 hora en ms

4. Entidad User

package com.example.auth.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password; // guardada en BCrypt

    // opcional: rol, nombre, email...
    private String role = "USER";

    // getters y setters
}

5. Repositorio UserRepository

package com.example.auth.repository;

import com.example.auth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    boolean existsByUsername(String username);
}

Obtén descuentos exclusivos de nuestros cursos en vivo en línea

Capacítate con los expertos

6. Servicio que implementa UserDetailsService

package com.example.auth.service;

import com.example.auth.model.User;
import com.example.auth.repository.UserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository repo;

    public CustomUserDetailsService(UserRepository repo) {
        this.repo = repo;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User u = repo.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));
        return new org.springframework.security.core.userdetails.User(
            u.getUsername(),
            u.getPassword(),
            List.of(new SimpleGrantedAuthority("ROLE_" + u.getRole()))
        );
    }
}

7. Utilitario JWT (JwtUtil)

package com.example.auth.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {

    private final Key key;
    private final long expirationMs;

    public JwtUtil(@Value("${jwt.secret}") String secret,
                   @Value("${jwt.expiration}") long expirationMs) {
        this.key = Keys.hmacShaKeyFor(secret.getBytes());
        this.expirationMs = expirationMs;
    }

    public String generateToken(String username) {
        Date now = new Date();
        Date exp = new Date(now.getTime() + expirationMs);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(exp)
                .signWith(key)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build()
                .parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException ex) {
            return false;
        }
    }
}

8. Filtro JWT (JwtAuthenticationFilter)

package com.example.auth.security;

import com.example.auth.service.CustomUserDetailsService;
import com.example.auth.util.JwtUtil;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.authentication.*;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import org.springframework.util.StringUtils;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final CustomUserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtUtil jwtUtil, CustomUserDetailsService uds) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = uds;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String bearer = request.getHeader("Authorization");
        if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
            String token = bearer.substring(7);
            if (jwtUtil.validateToken(token)) {
                String username = jwtUtil.getUsernameFromToken(token);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken auth =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        filterChain.doFilter(request, response);
    }
}

9. Configuración de seguridad (SecurityConfig)

package com.example.auth.config;

import com.example.auth.security.JwtAuthenticationFilter;
import com.example.auth.service.CustomUserDetailsService;
import com.example.auth.util.JwtUtil;
import org.springframework.context.annotation.*;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {

    private final CustomUserDetailsService uds;
    private final JwtUtil jwtUtil;

    public SecurityConfig(CustomUserDetailsService uds, JwtUtil jwtUtil) {
        this.uds = uds;
        this.jwtUtil = jwtUtil;
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider p = new DaoAuthenticationProvider();
        p.setUserDetailsService(uds);
        p.setPasswordEncoder(passwordEncoder());
        return p;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(jwtUtil, uds);

        http
          .csrf().disable()
          .authorizeHttpRequests()
            .requestMatchers("/auth/**", "/h2-console/**").permitAll()
            .anyRequest().authenticated()
          .and()
            .authenticationProvider(authProvider());

        // H2 console requires this in dev
        http.headers().frameOptions().disable();
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

10. Controlador de autenticación (AuthController)

package com.example.auth.controller;

import com.example.auth.model.User;
import com.example.auth.repository.UserRepository;
import com.example.auth.util.JwtUtil;
import org.springframework.http.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final UserRepository repo;
    private final BCryptPasswordEncoder encoder;
    private final JwtUtil jwtUtil;

    public AuthController(UserRepository repo, BCryptPasswordEncoder encoder, JwtUtil jwtUtil) {
        this.repo = repo;
        this.encoder = encoder;
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody Map<String, String> body) {
        String username = body.get("username");
        String password = body.get("password");
        if (repo.existsByUsername(username)) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Usuario ya existe");
        }
        User u = new User();
        u.setUsername(username);
        u.setPassword(encoder.encode(password));
        repo.save(u);
        return ResponseEntity.ok("Usuario registrado");
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody Map<String, String> body) {
        String username = body.get("username");
        String password = body.get("password");

        return repo.findByUsername(username).map(user -> {
            if (encoder.matches(password, user.getPassword())) {
                String token = jwtUtil.generateToken(username);
                return ResponseEntity.ok(Map.of("token", token));
            } else {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Credenciales inválidas");
            }
        }).orElseGet(() -> ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Usuario no encontrado"));
    }
}

11. Ejemplo de endpoint protegido

@RestController
@RequestMapping("/api")
public class ProtectedController {
    @GetMapping("/profile")
    public ResponseEntity<?> profile(Authentication authentication){
        return ResponseEntity.ok(Map.of("user", authentication.getName()));
    }
}

Este endpoint /api/profile solo responderá si la petición incluye Authorization: Bearer <token>.


12. Pruebas (curl)

  • Registrar:
curl -X POST http://localhost:8080/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"juan","password":"12345"}'
  • Login:
curl -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"juan","password":"12345"}'

Respuesta: {"token":"<JWT_AQUI>"}

  • Llamar endpoint protegido:
curl http://localhost:8080/api/profile \
  -H "Authorization: Bearer <JWT_AQUI>"

13. Buenas prácticas y consideraciones

  • Nunca guardes jwt.secret en el repo: usa variables de entorno o un secret manager.
  • Establece expiraciones razonables y soporte de refresh tokens si lo necesitas.
  • En entornos productivos, usa librerías y configuraciones que soporten revocación de tokens o almacena tokens inválidos si es necesario.
  • Añade validación y manejo de errores centralizado para respuestas limpias.
  • Protege rutas sensibles con roles/authorities (ej. ADMIN, USER).
  • Registra accesos y utiliza HTTPS.

14. Extensiones posibles (walk-up ideas)

  • Añadir refresh tokens.
  • Autenticación con OAuth2 / OpenID Connect (Keycloak, Auth0).
  • Verificación de email al registrarse.
  • Integración con base de datos real (Postgres) y migraciones Flyway/Liquibase.
  • Rate limiting en endpoints de autenticación.

15. Código completo y arranque

  • Estructura recomendada:
src/main/java/com/example/auth/
  model/User.java
  repository/UserRepository.java
  service/CustomUserDetailsService.java
  util/JwtUtil.java
  security/JwtAuthenticationFilter.java
  config/SecurityConfig.java
  controller/AuthController.java
  controller/ProtectedController.java
  AuthApplication.java
  • Ejecuta con mvn spring-boot:run y prueba los endpoints.

Tu código, tu control: aprende autenticación en Java. ¡Haz clic aquí!

About Author

Dale Tapia

0 0 votos
Article Rating
Suscribir
Notificar de
guest
0 Comments
La mas nueva
Más antiguo Más votada
Comentarios.
Ver todos los comentarios
0
¿Te gusta este articulo? por favor comentax