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);
}
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/profilesolo responderá si la petición incluyeAuthorization: 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.secreten 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:runy prueba los endpoints.
Tu código, tu control: aprende autenticación en Java.  ¡Haz clic aquí!
 
 
 

 
             
            

