Cómo configurar la autorización y autenticación de Java Spring Boot JWT

El mes pasado, tuve la oportunidad de implementar la autenticación JWT para un proyecto paralelo. Anteriormente trabajé con JWT en Ruby on Rails, pero esta fue mi primera vez en Spring.

En esta publicación, intentaré explicar lo que he aprendido y aplicado en mi proyecto para compartir mi experiencia y, con suerte, ayudar a algunas personas.

Comenzaremos echando un vistazo rápido a la teoría detrás de JWT y cómo funciona. Luego veremos cómo implementarlo en una aplicación Spring Boot.

Conceptos básicos de JWT

JWT, o JSON Web Tokens (RFC 7519), es un estándar que se utiliza principalmente para proteger las API REST. A pesar de ser una tecnología relativamente nueva, está ganando popularidad rápidamente.

En el proceso de autenticación de JWT, el front-end (cliente) envía primero algunas credenciales para autenticarse (nombre de usuario y contraseña en nuestro caso, ya que estamos trabajando en una aplicación web).

El servidor (la aplicación Spring en nuestro caso) luego verifica esas credenciales y, si son válidas, genera un JWT y lo devuelve.

Después de este paso, el cliente debe proporcionar este token en el encabezado de Autorización de la solicitud en el formulario "Bearer TOKEN". El back-end verificará la validez de este token y autorizará o rechazará las solicitudes. El token también puede almacenar roles de usuario y autorizar las solicitudes en función de las autoridades dadas.

Implementación

Ahora veamos cómo podemos implementar el mecanismo de inicio de sesión y guardado de JWT en una aplicación Spring real.

Dependencias

Puede ver la lista de dependencias de Maven que utiliza nuestro código de ejemplo a continuación. Tenga en cuenta que las dependencias centrales como Spring Boot e Hibernate no están incluidas en esta captura de pantalla.

Guardar usuarios

Comenzaremos creando controladores para guardar a los usuarios de forma segura y autenticarlos según el nombre de usuario y la contraseña.

Tenemos una entidad modelo llamada Usuario. Es una clase de entidad simple que se asigna a la tabla USER . Puede utilizar las propiedades que necesite según su aplicación.

También tenemos una clase UserRepository simple para salvar usuarios. Necesitamos anular el método findByUsername ya que lo usaremos en la autenticación.

public interface UserRepository extends JpaRepository{ User findByUsername(String username); }

Nunca debemos almacenar contraseñas de texto sin formato en la base de datos porque muchos usuarios tienden a usar la misma contraseña para múltiples sitios.

Hay muchos algoritmos hash diferentes, pero el más utilizado es BCrypt y es un método recomendado de hash seguro. Puede consultar este artículo para obtener más información sobre el tema.

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

Para hash de la contraseña, vamos a definir una Bcrypt frijol en @SpringBootApplication y anotar la clase principal de la siguiente manera:

Llamaremos a los métodos en este bean cuando necesitemos hash una contraseña.

También necesitamos un UserController para salvar usuarios. Creamos el controlador, lo anotamos con @RestController y definimos el mapeo correspondiente.

En nuestra aplicación, guardamos al usuario en función de un objeto DTO que se pasa desde la interfaz. También puede pasar un objeto Usuario en @RequestBody .

Después de pasar el objeto DTO, encriptamos el campo de contraseña usando el bean BCrypt que creamos anteriormente. También puede hacer esto en el controlador, pero es una mejor práctica poner esta lógica en la clase de servicio.

@Transactional(rollbackFor = Exception.class) public String saveDto(UserDto userDto) { userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword())); return save(new User(userDto)).getId(); }

Filtro de autenticación

Necesitamos autenticación para asegurarnos de que el usuario sea realmente quien dice ser. Usaremos el par clásico de nombre de usuario / contraseña para lograr esto.

Estos son los pasos para implementar la autenticación:

  1. Cree nuestro filtro de autenticación que amplíe UsernamePasswordAuthenticationFilter
  2. Cree una clase de configuración de seguridad que amplíe WebSecurityConfigurerAdapter y aplique el filtro

Aquí está el código de nuestro filtro de autenticación; como ya sabrá, los filtros son la columna vertebral de Spring Security.

Repasemos este código paso a paso.

Esta clase extiende UsernamePasswordAuthenticationFilter, que es la clase predeterminada para la autenticación de contraseña en Spring Security. Lo ampliamos para definir nuestra lógica de autenticación personalizada.

Hacemos una llamada al método setFilterProcessesUrl en nuestro constructor. Este método establece la URL de inicio de sesión predeterminada en el parámetro proporcionado.

Si elimina esta línea, Spring Security crea el punto final "/ login" de forma predeterminada. Define el punto final de inicio de sesión para nosotros, por lo que no definiremos un punto final de inicio de sesión en nuestro controlador explícitamente.

Después de esta línea, nuestro punto final de inicio de sesión será / api / services / controller / user / login . Puede utilizar esta función para mantener la coherencia con sus puntos finales.

Sobreescribimos los attemptAuthentication y successfulAuthentication métodos de la UsernameAuthenticationFilter clase.

La función intentAuthentication se ejecuta cuando el usuario intenta iniciar sesión en nuestra aplicación. Lee las credenciales, crea un POJO de usuario a partir de ellas y luego verifica las credenciales para autenticarse.

Pasamos el nombre de usuario, la contraseña y una lista vacía. La lista vacía representa las autoridades (roles), y la dejamos como está ya que aún no tenemos ningún rol en nuestra aplicación.

Si la autenticación es satisfactoria, el successfulAuthentication método se ejecuta. Spring Security pasa los parámetros de este método detrás de escena.

The attemptAuthentication method returns an Authentication object that contains the authorities we passed while attempting.

We want to return a token to user after authentication is successful, so we create the token using username, secret, and expiration date. We need to define the SECRET and EXPIRATION_DATE now.

We create a class to be a container for our constants. You can set the secret to whatever you want, but the best practice is making the secret key as long as your hash. We use the HS256 algorithm in this example, so our secret key is 256 bits/32 chars.

The expiration time is set to 15 minutes, because it is the best practice against secret key brute-forcing attacks. The time is in milliseconds.

We have prepared our Authentication filter, but it is not active yet. We also need an Authorization filter, and then we will apply them both through a configuration class.

This filter will check the existence and validity of the access token on the Authorization header. We will specify which endpoints will be subject to this filter in our configuration class.

Authorization Filter

The doFilterInternal method intercepts the requests then checks the Authorization header. If the header is not present or doesn’t start with “BEARER”, it proceeds to the filter chain.

If the header is present, the getAuthentication method is invoked. getAuthentication verifies the JWT, and if the token is valid, it returns an access token which Spring will use internally.

This new token is then saved to SecurityContext. You can also pass in Authorities to this token if you need for role-based authorization.

Our filters are ready, and now we need to put them into action with the help of a configuration class.

Configuration

We annotate this class with @EnableWebSecurity and extend WebSecurityConfigureAdapter to implement our custom security logic.

We autowire the BCrypt bean that we defined earlier. We also autowire the UserDetailsService to find the user’s account.

The most important method is the one which accepts an HttpSecurity object. Here we specify the secure endpoints and filters that we want to apply. We configure CORS, and then we permit all post requests to our sign up URL that we defined in the constants class.

You can add other ant matchers to filter based on URL patterns and roles, and you can check this StackOverflow question for examples regarding that. The other method configures the AuthenticationManager to use our encoder object as its password encoder while checking the credentials.

Testing

Let’s send a few requests to test if it works properly.

Here we send a GET request to access a protected resource. Our server responds with a 403 code. This is the expected behavior because we haven’t provided a token in the header. Now let’s create a user:

To create a user, we send a post request with our User DTO data. We will use this user to login and get an access token.

Great! We got the token. After this point, we will use this token to access protected resources.

We provide the token in the Authorization header and we are now allowed access to our protected endpoint.

Conclusion

In this tutorial I have walked you through the steps I took when implementing JWT authorization and password authentication in Spring. We also learned how to save a user securely.

Thank you for reading – I hope it was helpful to you. If you are interested in reading more content like this, feel free to subscribe to my blog at //erinc.io. :)