CodeGym /Cursos /Módulo 5. Spring /Lección 103: Conceptos básicos de JSON Web Token (JWT) y ...

Lección 103: Conceptos básicos de JSON Web Token (JWT) y su estructura

Módulo 5. Spring
Nivel 18 , Lección 2
Disponible

¡Bienvenido a uno de los temas más interesantes relacionados con la seguridad en las aplicaciones web modernas! Hoy nos vamos a sumergir en el mundo de JSON Web Token (JWT), que se ha convertido en el estándar de facto para autenticación y autorización, especialmente en arquitecturas de microservicios. Si oyes la sigla JWT por primera vez, no te preocupes, al final de la clase todo estará "codificado", y lo digo literalmente.


¿Qué es JSON Web Token (JWT)?

JSON Web Token (se pronuncia "Jota-Ue-Té", aunque si tienes sentido del humor puedes decir "Javoti") — es una forma compacta, autocontenida de transmitir información entre dos partes en forma de objeto JSON. JWT se usa para autenticación y transmisión de información de manera que se pueda verificar sin contactar al servidor.

Veamos en qué consiste su magia:

  1. Compactness: JWT es una cadena que se transmite fácilmente por URL, en cabeceras HTTP o en el cuerpo de la petición.
  2. Self-contained: todo el "contexto" (por ejemplo, datos del usuario) está dentro del token.
  3. Security: los tokens están firmados, por lo que nadie puede falsificar sus datos. Si se usa un algoritmo HMAC o RSA, los datos también pueden cifrarse.

Estructura del JWT

Un JWT consiste en tres partes separadas por puntos (.):


header.payload.signature

1.Header (encabezado): contiene metadatos sobre el JWT, por ejemplo el tipo de token (normalmente "JWT") y el algoritmo de firma (por ejemplo, HMAC SHA256 o RSA).

Ejemplo:


{
  "alg": "HS256",
  "typ": "JWT"
}

Esta parte del token se codifica en Base64Url.

2. Payload (carga útil): es la parte principal del token, que contiene los datos. Aquí pueden ir claims arbitrarios — afirmaciones que el token le comunica al sistema.

Ejemplo de payload:


{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "exp": 1633024800
}

Fíjate en lo siguiente:

  • sub — identificador del usuario.
  • exp (expiration) — tiempo de expiración del token (número en segundos desde la epoch Unix, después del cual el token ya no es válido).

3. Signature (firma): se crea para garantizar la integridad del token. Se calcula usando el algoritmo del header, los header y payload codificados, y una clave secreta.

La fórmula de la firma se ve así:


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Ejemplo de un JWT "armado":


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

¿Cómo funciona JWT en la vida real?

Autenticación:

  1. El usuario envía sus credenciales (login y password) al servidor.
  2. El servidor verifica las credenciales y, si todo está bien, genera un JWT.
  3. El JWT se devuelve al usuario, que lo almacena (normalmente en localStorage o en una cookie).
  4. En cada petición al servidor, el cliente envía el JWT en la cabecera Authorization.

Ejemplo de cabecera:


Authorization: Bearer <your-jwt-token>

Autorización:

Con la información que contiene el payload, el servidor puede determinar los roles del usuario (por ejemplo, admin, user) y dar acceso a recursos protegidos.


Ejemplo de uso de JWT en Spring Boot

Vamos al grano con la práctica. Crearemos una aplicación Spring Boot que genera y valida JWT.

Dependencia en pom.xml

Añadimos las librerías necesarias para trabajar con JWT:


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

Generación de JWT

Creemos un servicio para generar tokens:


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    private String secret = "mySecretKey"; // ¡Nunca guardes el secreto en el código en proyectos reales!

    // Generación del token
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

    // Creación del token
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }
}

Descifrado y verificación de JWT

Vamos a añadir un método para verificar el token:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtUtil {

    private String secret = "mySecretKey";

    // Проверка токена
    public Claims extractClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    public String extractUsername(String token) {
        return extractClaims(token).getSubject();
    }
}
}

Ahora el servidor puede decodificar el token y extraer información útil de él, por ejemplo el nombre del usuario.


Protección de REST API

En Spring Security podemos añadir un filtro que verifique el JWT en cada petición. Ejemplo de filtro:


import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

public class JwtFilter extends OncePerRequestFilter {

    private JwtUtil jwtUtil = new JwtUtil();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws java.io.IOException, jakarta.servlet.ServletException {

        String authorizationHeader = request.getHeader("Authorization");

        String token = null;
        String username = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            token = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(token);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // En este caso verificaríamos al usuario (por ejemplo, contra la base de datos).
            // El código de verificación se ha omitido por simplicidad.
        }

        chain.doFilter(request, response);
    }
}

Ese filtro se conecta en tu configuración de Spring Security.


Errores típicos al trabajar con JWT

  1. Secretos en el código. Nunca guardes la clave secreta directamente en la aplicación. Usa gestores de secretos o variables de entorno.
  2. Tokens no renovables. Si el token expira y no tienes un mecanismo de Refresh tokens, los usuarios tendrán que autenticarse de nuevo.
  3. Saturación del payload. Recuerda que todos los datos del token se envían al cliente. Información innecesaria puede ralentizar el sistema y crear riesgos de seguridad.

Ten en cuenta que JWT es una herramienta potente, pero no te protege de todos los problemas de seguridad. Úsala con cabeza, y estarás en la cima.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION