Cómo implementar autorización y autenticación JWT en Java Spring Boot

Go to Homepage

En este artículo, exploraremos cómo implementar autenticación y autorización con JWT en Java Spring Boot

En este artículo, exploraremos cómo implementar autenticación y autorización con JWT en Java Spring Boot. JWT, siglas en inglés para “JSON Web Tokens”, es un estándar abierto que define una forma compacta y segura de transmitir información entre dos partes. Es especialmente útil para la autenticación y autorización en aplicaciones web y móviles.

A lo largo de este artículo, proporcionaremos un ejemplo de cómo implementar JWT en una aplicación web. Primero, describiremos cómo agregar la dependencia necesaria a Spring Boot, luego crearemos un controlador de autenticación y un controlador de recursos que utilicen JWT para la autenticación y la autorización. Además, enseñaremos cómo agregar anotaciones de seguridad para proteger ciertas rutas en nuestra aplicación.

Para comenzar, necesitamos agregar la dependencia jjwt a nuestro archivo pom.xml correspondiente. Podemos hacer esto mediante la inclusión de la sigiuente sección:

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

Ahora, podemos crear un controlador de autenticación que maneje el proceso de inicio de sesión. En nuestro ejemplo, usaremos un formulario de inicio de sesión que consiste en un nombre de usuario y una contraseña. El controlador de autenticación verificará las credenciales del usuario y proporcionará un JWT si son correctas.

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

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody LoginForm loginRequest) {

        Optional<User> userOptional = userRepository.findByUsername(loginRequest.getUsername());

        if (!userOptional.isPresent()) {
            return ResponseEntity.badRequest().body("User not found");
        }

        User user = userOptional.get();

        if (!user.getPassword().equals(loginRequest.getPassword())) {
            return ResponseEntity.badRequest().body("Incorrect password");
        }

        String jwt = jwtUtils.generateJwtToken(user);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }
}

En este código, primero obtenemos el usuario del repositorio de usuarios utilizando el nombre de usuario proporcionado en el formulario de inicio de sesión. Luego, verificamos si la contraseña proporcionada coincide con la almacenada en la base de datos. Si las credenciales son correctas, generamos un JWT utilizando la clase JwtUtils y lo devolvemos en el cuerpo de la respuesta.

Ahora podemos crear un controlador de recursos que use el JWT para la autenticación y autorización de ciertas rutas en nuestra aplicación. En este ejemplo, creamos un controlador de mensajes que solo puede ser accedido por usuarios autenticados.

@RestController
@RequestMapping("/api/messages")
public class MessageController {

    @GetMapping("")
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<?> getAllMessages() {
        List<Message> messages = messageService.getAllMessages();
        return ResponseEntity.ok(messages);
    }
}

En este código, estamos usando la anotación @PreAuthorize para indicar que solo los usuarios autenticados pueden acceder a esta ruta. Si un usuario no está autenticado, se le negará el acceso.

En resumen, hemos explorado cómo implementar autenticación y autorización con JWT en Java Spring Boot. Hemos creado un controlador de autenticación que maneja el proceso de inicio de sesión, un controlador de recursos que utiliza JWT para permitir o denegar el acceso a ciertas rutas de nuestra aplicación, y hemos agregado anotaciones de seguridad para proteger nuestro recurso de mensajes.

Qué es JWT y cómo funciona

JWT significa “JSON Web Token” y es un estándar abierto para transmitir información de manera segura entre dos partes. En términos sencillos, un JWT es una cadena de caracteres que se utiliza como un objeto seguro para transmitir información.

Un JWT se compone de tres partes separadas por un punto: la cabecera (header), la carga útil (payload) y la firma (signature).

La cabecera contiene información sobre el tipo de token y el algoritmo de criptografía utilizado para firmarlo. Por ejemplo, una cabecera puede contener "alg": "HS256", que indica que se utiliza el algoritmo de firma HMAC-SHA256.

La carga útil contiene la información que se desea transmitir de forma segura. Esto puede incluir datos como el nombre de usuario, la fecha de caducidad y otros metadatos relevantes para la aplicación.

La firma se utiliza para asegurar que el mensaje no ha sido manipulado en el camino. La firma se genera utilizando una clave secreta que sólo las partes autorizadas conocen. Cuando la parte que recibe el token recibe la firma, utiliza la misma clave secreta para verificar que la firma es válida.

Una vez que se ha generado un JWT, se puede transmitir de forma segura entre dos partes, por ejemplo, entre un cliente y un servidor. La parte que recibe el token puede utilizarlo para autenticar al usuario y autorizarlo para realizar ciertas acciones o acceder a ciertos recursos.

JWT es un estándar de seguridad abierto que permite la transmisión de información sensible de forma segura entre dos partes. Se compone de tres partes, la cabecera, la carga útil y la firma, que se utilizan para transmitir y verificar la información. Es una herramienta muy útil para implementar la autenticación y autorización en aplicaciones web modernas.

Configuración inicial de Spring Boot

Para comenzar a implementar autorización y autenticación JWT en Java Spring Boot, es necesario tener una configuración inicial en el proyecto.

Lo primero es instalar las dependencias necesarias en el archivo pom.xml. Agrega las siguientes dependencias para Spring Security y JWT:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

Estas dependencias nos permitirán utilizar la seguridad de Spring para autenticar usuarios y la librería JWT para generar y verificar los tokens de autenticación.

Luego, a continuación, habilita la configuración de seguridad en la aplicación spring boot añadiendo la siguiente anotación en la clase principal de la aplicación:

@EnableWebSecurity
public class YourApplication {
    // ...
}

Esto activará la seguridad en el proyecto y nos permitirá hacer configuraciones adicionales.

Para las opciones de seguridad, se requiere tener acceso solo a ciertas rutas, en este caso necesitamos permitir el acceso para algunas rutas permitidas. Agrega la siguiente clase de configuración de seguridad:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated();
    }
}

La clase SecurityConfig extenderá WebSecurityConfigurerAdapter para permitir la configuración de seguridad. En la configuración, las rutas que comiencen con /api/auth/ están abiertas y cualquier otra requiere autenticación.

Con estas configuraciones básicas, estamos listos para implementar la lógica de autenticación y autorización usando JWT y Spring Boot.

Conclusión: La configuración inicial en Java Spring Boot para implementar autorización y autenticación JWT es fácil de establecer con el uso de dependencias y la configuración de seguridad. Al agregar estas dependencias y configuraciones, ya podemos comenzar a construir una aplicación segura que utilice JWT para la autenticación y la autorización.

Creación de tokens JWT y su verificación

La creación de tokens JWT es una tarea sencilla gracias a la librería de JWT que se integra fácilmente a Spring Boot. Primero se debe incluir la dependencia de la librería en nuestro archivo pom.xml:

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

A continuación, se debe crear un método que genere el token, este método debe recibir como parámetro un objeto de tipo UserDetails que contenga la información del usuario autenticado:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;

public String generateToken(UserDetails userDetails) {
    // tiempo de expiración del token (en milisegundos)
    final long expiration = 3600000; // 1 hora
    // clave secreta para firmar el token
    final String secret = "miClaveSecreta";
    // se crea el token
    return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
}

Es importante destacar que el método signWith recibe como segundo parámetro la clave secreta con la que se va a firmar el token.

Para verificar el token, se debe crear un método que valide la firma del token y que extraiga la información del usuario autenticado:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.function.Function;

public boolean validateToken(String token, UserDetails userDetails) {
    // clave secreta para verificar el token
    final String secret = "miClaveSecreta";
    // se verifica la firma del token y se extraen sus claims
    final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    // se verifica que el nombre de usuario en los claims sea igual al del userDetails
    final String username = extractUsername(token);
    return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}

private boolean isTokenExpired(String token) {
    return extractExpiration(token).before(new Date());
}

private Date extractExpiration(String token) {
    return extractClaim(token, Claims::getExpiration);
}

private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = extractAllClaims(token);
    return claimsResolver.apply(claims);
}

private Claims extractAllClaims(String token) {
    return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}

private String extractUsername(String token) {
    return extractClaim(token, Claims::getSubject);
}

En este método se utilizan funciones lambda para extraer los claims del token. Es importante verificar que el token no haya expirado y que el nombre de usuario sea el mismo que el del UserDetails.

La creación y verificación de tokens JWT es una tarea sencilla gracias a la librería de JWT que se integra fácilmente a Spring Boot. Con estos métodos ya podemos implementar la autenticación y autorización en nuestra aplicación.

Implementación de autenticación en el backend

Para implementar la autenticación en el backend, vamos a utilizar Spring Security, el cual es un marco de seguridad que proporciona autenticación y autorización para aplicaciones web. En combinación con JSON Web Tokens (JWT), lograremos una autenticación segura y sin estado.

Primero, necesitamos agregar las dependencias en nuestro archivo pom.xml:

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

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

A continuación, creamos una clase que extienda de WebSecurityConfigurerAdapter para configurar la seguridad en nuestra aplicación:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // configurar autenticación
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    }

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

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // configurar autorización
        httpSecurity.csrf().disable()
                .authorizeRequests().antMatchers("/authenticate").permitAll()
                .anyRequest().authenticated().and()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // añadir filtro JWT
        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

En esta clase, configuramos la autenticación y autorización, así como el filtro que se encargará de verificar y validar el token JWT en todas las solicitudes.

Además, necesitamos una clase que implemente UserDetailsService, la cual será responsable de cargar los detalles del usuario en la base de datos:

@Service
public class JwtUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                new ArrayList<>());
    }
}

Finalmente, creamos una clase JwtAuthenticationEntryPoint para manejar los errores de autenticación:

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

Con estas clases, habremos configurado correctamente la autenticación y autorización con JWT en nuestro backend.

Creación de roles y permisos de usuario

Para implementar un sistema de autenticación y autorización efectivo, es importante definir los roles y permisos de los usuarios de la aplicación. En Java Spring Boot, podemos utilizar la librería Spring Security para gestionar los roles y permisos.

Para empezar, debemos definir los roles de usuario que estarán disponibles en nuestra aplicación. Estos roles pueden ser ADMIN, USER, MANAGER, etc. Para agregar roles a nuestra aplicación, podemos crear una tabla en nuestra base de datos con los roles disponibles. En esta tabla, podemos definir el nombre del rol y una descripción breve.

Una vez que hemos definido los roles disponibles, podemos definir los permisos de cada rol. Estos permisos pueden ser CRUD (Crear, Leer, Actualizar, Eliminar) para cada entidad de la aplicación, como usuarios, productos, pedidos, etc. Para agregar permisos a nuestra aplicación, podemos crear una tabla en nuestra base de datos con los permisos disponibles. En esta tabla, podemos definir el nombre del permiso y una descripción breve.

Para asignar roles y permisos a los usuarios, podemos crear una tabla intermedia en nuestra base de datos que relacione los roles con los usuarios y los permisos con los roles. De esta manera, podemos definir qué roles y permisos tiene cada usuario de nuestra aplicación.

Una vez que hemos definido los roles y permisos de nuestra aplicación, podemos configurar Spring Security para gestionar la autenticación y autorización de los usuarios. En la configuración de Spring Security, podemos definir qué roles tienen acceso a cada recurso de nuestra aplicación y qué permisos son necesarios para acceder a cada recurso.

En resumen, para implementar la autenticación y autorización JWT en Java Spring Boot, es importante definir los roles y permisos de los usuarios de nuestra aplicación. Podemos agregar roles y permisos mediante tablas en nuestra base de datos y utilizar la librería Spring Security para gestionar el acceso a los recursos de la aplicación.

Protección de recursos con Spring Security

Para proteger los recursos de nuestra aplicación, necesitamos configurar Spring Security. En primer lugar, agregamos la dependencia de Spring Security en nuestro archivo pom.xml o build.gradle.

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

Una vez que tenemos la dependencia, podemos configurar Spring Security para asegurar nuestros recursos, es decir, asegurarnos de que los usuarios que intenten acceder a ellos estén autenticados y autorizados.

En nuestro archivo de configuración SecurityConfig.java, extendemos la clase WebSecurityConfigurerAdapter y anulamos su método configure(HttpSecurity http). Aquí, podemos definir nuestras reglas de seguridad.

En primer lugar, podemos especificar quién puede acceder a qué recursos. Por ejemplo, podemos permitir que todos los usuarios no autenticados accedan a la página de inicio de sesión, pero restringir el acceso a otras páginas a solo usuarios autenticados.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/login").anonymous()
        .anyRequest().authenticated()
        .and().formLogin()
        .and().httpBasic();
}

En este ejemplo, permitimos que cualquier usuario no autenticado acceda a la página de inicio de sesión (/login), pero restringimos el acceso a cualquier otra página a solo usuarios autenticados (anyRequest().authenticated()). También proporcionamos la configuración para el inicio de sesión (formLogin()) y la autenticación básica de HTTP (httpBasic()).

Además de restringir el acceso a ciertos recursos, también podemos controlar quién tiene ciertos permisos. Usando anotaciones como @PreAuthorize y @PostAuthorize, podemos especificar que ciertas funciones solo deben ser accesibles para ciertos usuarios o roles.

@PreAuthorize("hasRole('ROLE_ADMIN')")
public void delete(User user) {
    // código para eliminar al usuario
}

En este ejemplo, la función delete() solo puede ser llamada por usuarios que tengan el rol de administrador.

Spring Security nos proporciona una forma fácil de proteger nuestros recursos al implementar la autenticación y autorización en nuestra aplicación. Al usar la clase WebSecurityConfigurerAdapter y configurar nuestras reglas de seguridad, podemos asegurarnos de que solo los usuarios autorizados tengan acceso a ciertos recursos. También podemos usar anotaciones como @PreAuthorize y @PostAuthorize para controlar quién tiene ciertos permisos en nuestra aplicación.

Cómo gestionar los errores de autenticación

La autenticación es un proceso crucial en cualquier aplicación, ya que garantiza que los usuarios sean quienes dicen ser. Sin embargo, este proceso también puede generar errores y es importante saber cómo manejarlos adecuadamente.

En la implementación de autenticación y autorización JWT en Java Spring Boot, es necesario definir cómo se van a manejar los errores de autenticación. La utilización de mensajes de error claros y precisos es fundamental para asegurar que el usuario final comprenda el problema y pueda corregir sus credenciales de acceso o solicitar ayuda en caso de ser necesario.

Es recomendable utilizar el manejador de excepciones de Spring para interceptar y procesar estos errores. Para ello, se puede crear una clase que extienda de ResponseEntityExceptionHandler y sobrescribir los métodos que se utilizarán para manejar los diferentes errores.

Por ejemplo, si se presenta un error de autenticación con credenciales inválidas, se puede sobrescribir el método “handleBadCredentialsException” que permitirá devolver un mensaje claro al usuario:

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity handleBadCredentialsException(BadCredentialsException e) {
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
          .body("Credenciales inválidas. Verifique su usuario y contraseña.");
}

De esta manera, cuando se presente este tipo de error, se enviará una respuesta HTTP al cliente con un estado de “Unauthorized” y un cuerpo con el mensaje “Credenciales inválidas. Verifique su usuario y contraseña.”.

También es importante definir cómo se van a manejar otros errores como la expiración del token, la falta de token en la solicitud, o cualquier otro error que se pueda presentar durante el proceso de autenticación.

Para implementar una autenticación efectiva en Java Spring Boot es necesario definir cómo se van a manejar los errores de autenticación y utilizar mensajes de error claros y precisos para ayudar al usuario a entender y resolver el problema. Además, se puede utilizar ResponseEntityExceptionHandler para interceptar y procesar estos errores con el fin de ofrecer una mejor experiencia de usuario.

Uso de JWT para aplicaciones móviles

Una de las ventajas de utilizar JSON Web Tokens (JWT) en una aplicación móvil es que permite la autenticación y autorización de usuarios de manera segura y eficiente.

En una aplicación móvil, el proceso de autenticación comienza cuando el usuario ingresa sus credenciales y las envía al servidor. En el lado del servidor, se verifica la información proporcionada y, si es correcta, se genera un token JWT.

Este token se envía al cliente y se almacena en la memoria del dispositivo móvil o en el almacenamiento local. A partir de ese momento, el token se utilizará en cada solicitud de recursos protegidos para autenticar al usuario.

En la implementación de JWT en una aplicación móvil, es importante tener en cuenta las medidas de seguridad necesarias para proteger el token, como almacenar el token en un lugar seguro y utilizar HTTPS para todas las solicitudes de recursos protegidos.

En cuanto a la autorización, JWT permite incluir información adicional en el token, como el rol del usuario o los permisos que tiene. De esta manera, se puede controlar el acceso a los diferentes recursos de la aplicación móvil y asegurarse de que los usuarios solo puedan acceder a los que tienen permitidos.

Es importante mencionar que, al utilizar JWT en una aplicación móvil, se deben definir correctamente los tiempos de expiración del token para evitar posibles vulnerabilidades. También se recomienda utilizar bibliotecas de JWT confiables y mantenerlas actualizadas para asegurar la protección de la autenticación y autorización de los usuarios.

En resumen, el uso de JWT en una aplicación móvil permite una autenticación y autorización eficiente y segura. Es fundamental tener en cuenta las medidas de seguridad necesarias para proteger el token y definir correctamente los tiempos de expiración para garantizar la protección de los usuarios y la información de la aplicación móvil.

El uso de JSON Web Tokens (JWT) se ha convertido en una solución popular para la autenticación y autorización en aplicaciones móviles. Con esta tecnología se pueden garantizar niveles adecuados de seguridad, facilitando a la vez el acceso a los recursos protegidos de la aplicación por parte de los usuarios.

Ventajas y desventajas de JWT en la autenticación y autorización

Las JWT (JSON Web Tokens) ofrecen una serie de ventajas en la autenticación y autorización que las hacen una buena elección en muchos proyectos. Por otro lado, también tienen ciertas desventajas que deben ser consideradas al elegir este método de seguridad.

Entre las ventajas de la autenticación y autorización JWT se encuentran:

Simplicidad

Las JWTs son fáciles de entender y utilizar, ya que están basadas en un estándar abierto y bien documentado.

Seguridad

Las JWTs son una forma segura de transmitir información entre diferentes sistemas gracias a su estructura criptográfica. Además, tienen un tiempo de vida limitado, de manera que una vez expirado deben ser renovadas para mantener la seguridad.

Escalabilidad

Las JWTs permiten a los sistemas escalables el autenticar y autorizar usuarios sin tener que rastrear inmediatamente una conexión de sesión en una base de datos. Esto es útil para aplicaciones ampliamente escalables y distribuidas.

Herramientas de soporte

JWT ha existido por muchos años, por lo cual en la actualidad existen muchas herramientas que brindan soporte a la autenticación y autorización JWT a través de múltiples lenguajes de programación, lo que disminuye la capacidad de error humano.

Por otro lado, hay algunas desventajas que deben ser consideradas al utilizar la autenticación y autorización JWT, las cuales son:

Eficiencia

Comparado a otros métodos de autenticación y autorización, el proceso de JWT puede ser lento y pesado para el servidor.

Espacio de almacenamiento

A pesar de estar optimizado, las JWT pueden resultar pesadas en términos de almacenamiento. Debido a que los JWT contienen información del usuario, si el usuario escribe demasiada información o si el sitio web tiene muchos usuarios, el volumen de almacenamiento puede ser significativo.

No revocable

JWTs no son revocables, lo cual significa que una vez que se emite un token, no se puede cancelar hasta que expire de manera natural. Esto tiene implicaciones si un token se forma imprudentemente, como en un ataque de phishing.

JWT es un método sólido de autenticación y autorización en la web, con sus puntos fuertes y debilidades. Discordar o afirmar esto en función de escenarios individuales es una tarea de cada equipos, y conviene planificar y considerar los posibles puntos de falla de antemano antes de decidir utilizar este método.

Otros Artículos