Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. JSON Web Token
- 4. Estructura del proyecto
- 5. Spring Security
- 6. Implementación
- 7. Probando…probando
- 8. Conclusiones
- 9. Referencias
1. Introducción
En este tutorial veremos cómo securizar un API REST empleando JSON Web Tokens (JWT). Para este tutorial utilizaremos un API muy simple donde activaremos Spring Security con la configuración adecuada e implementaremos las clases necesarias para el uso de JWT.
En anteriores tutoriales vimos con securizar un API REST utilizando Node.js y JWT, en esta ocasion utilizaremos Spring Boot ya que nos permite desarrollar rápidamente API REST con el mínimo código y por tanto con el menor número de errores 😉
Disponéis de un tutorial de Natalia Roales en el portal sobre proyectos con Spring Boot y os dejamos el código fuente de este tutorial en github para vuestra consulta.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil Mac Book Pro 15″ (2,5 Ghz Intel Core i7, 16 GB DDR3)
- Sistema Operativo: Mac OS Sierra 10.12.6
- Entorno de desarrollo: Spring Tool Suite 3.9.0
3. JSON Web Token
JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define un modo compacto y autónomo para transmitir de forma segura la información entre las partes como un objeto JSON. Esta información puede ser verificada y es confiable porque está firmada digitalmente. Los JWT se pueden firmar usando un secreto (con el algoritmo HMAC) o utilizando un par de claves públicas / privadas usando RSA.
3.1. Ventajas de los tokens frente a las cookies
Quizás la mayor ventaja de los tokens sobre las cookies es el hecho de que no tenga estado (stateless). El backend no necesita mantener un registro de los tokens. Cada token es compacto y auto contenido. Contiene todos los datos necesarios para comprobar su validez, así como la información del usuario para las diferentes peticiones.
El único trabajo del servidor consiste en firmar tokens al iniciar la sesión y verificar que los tokens intercambiados sean válidos. Esta característica permite la escalabilidad inmediata ya que las peticiones no dependen unas de otras. De esta forma se pueden tramitar en diferentes servidores de forma autónoma.
Las cookies funcionan bien con dominios y subdominios únicos, pero cuando se trata de administrar cookies en diferentes dominios, puede volverse complejo. El enfoque basado en tokens con Cross Origin Resource Sharing (CORS) habilitado hace trivial exponer las API a diferentes servicios y dominios.
Con un enfoque basado en cookies, simplemente se almacena el ID de sesión en una cookie. JWT por otro lado permiten almacenar cualquier tipo de metadatos, siempre y cuando sea un JSON válido.
Si os preguntáis por el rendimiento, cuando se utiliza la autenticación basada en cookies, el backend tiene que hacer una búsqueda habitualmente una base de datos para recuperar la información del usuario, esto seguramente supera el tiempo que pueda tomar la decodificación de un token. Además, puesto que se pueden almacenar datos adicionales dentro del JWT, por ejemplo, permisos de usuario, puede ahorrarse llamadas adicionales para la búsqueda de esta información.
Recordad que el token habitualmente va firmado pero no va cifrado. En la web de jwt.io disponéis de un depurador que permite consultar y comprobar la validez del mismo.
Después de tanta teoría vamos a lo interesante.
4. Estructura del proyecto
Nuestro proyecto a nivel de Maven está configurado para ser un proyecto Spring Boot de tipo web, que utiliza Spring Security, JPA y HSQLDB (podéis consultar el pom.xml). Veamos las partes más importantes del ejemplo, consta de un controlador para acceso al API REST, el acceso a la capa de datos por JPA y los beans de dominio, muy simple. Para esta demostración utilizaremos la base de datos en memoria HSQLDB (HyperSQL Database).
A nivel de controlador se disponen de métodos para crear un usuario, consultar todos o consultar uno concreto. Para evitar almacenar las password en plano, aplicamos una función de Hash basada en el cifrado Blowfish (BCrypt). Recordad que el uso de MD5 para almacenar las password no se recomienda, utilizad algoritmos más modernos.
UsuarioController.java
package com.autentia.demo.jwt.usuario; import java.util.List; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class UsuarioController { private UsuarioRepository usuarioRepository; private BCryptPasswordEncoder bCryptPasswordEncoder; public UsuarioController(UsuarioRepository usuarioRepository, BCryptPasswordEncoder bCryptPasswordEncoder) { this.usuarioRepository = usuarioRepository; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } @PostMapping("/users/") public void saveUsuario(@RequestBody Usuario user) { user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); usuarioRepository.save(user); } @GetMapping("/users/") public List<Usuario> getAllUsuarios() { return usuarioRepository.findAll(); } @GetMapping("/users/{username}") public Usuario getUsuario(@PathVariable String username) { return usuarioRepository.findByUsername(username); } }
5. Spring Security
Gracias a Spring Security podemos incorporar mecanismos potentes para proteger nuestras aplicaciones utilizando una cantidad mínima de código.
En nuestro caso indicamos a Spring Security que proteja todas las URLs excepto la URL de login, así mismo, declaramos las implementaciones que utilizaremos para realizar la autenticación y autorización.
WebSecurity.java
package com.autentia.demo.jwt.security; import static com.autentia.demo.jwt.security.Constants.LOGIN_URL; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity public class WebSecurity extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService; public WebSecurity(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { /* * 1. Se desactiva el uso de cookies * 2. Se activa la configuración CORS con los valores por defecto * 3. Se desactiva el filtro CSRF * 4. Se indica que el login no requiere autenticación * 5. Se indica que el resto de URLs esten securizadas */ httpSecurity .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .cors().and() .csrf().disable() .authorizeRequests().antMatchers(HttpMethod.POST, LOGIN_URL).permitAll() .anyRequest().authenticated().and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // Se define la clase que recupera los usuarios y el algoritmo para procesar las passwords auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); return source; } }
Podemos observar que se ajusta la configuración para CORS y se desactiva el filtro de Cross-site request forgery (CSRF). Esto nos permite habilitar el API para cualquier dominio, esta es una de las grandes ventajas del uso de JWT.
6. Implementación
En el siguiente diagrama podéis observar el flujo habitual de una aplicación securizada. Si lo comparamos al flujo seguido en la autenticación vía cookies, es muy similar.
Por fin llegamos a la lógica de negocio a utilizar para autenticar y autorizar nuestras peticiones. Para simplificar este tutorial, se verificará únicamente que exista el usuario y password en nuestra base de datos. Se podría incorporar un modelo más complejo incorporando permisos y roles pero se aleja del objetivo de este tutorial. Si os interesa estos temas, podéis consultar su manejo en la documentación de Spring Security.
6.1. Autenticación
Haciendo uso de las clases proporcionadas por Spring Security, extendemos su comportamiento para reflejar nuestras necesidades. Se verifica que las credencias proporcionadas son válidas y se genera el JWT.
JWTAuthenticationFilter.java
package com.autentia.demo.jwt.security; import static com.autentia.demo.jwt.security.Constants.HEADER_AUTHORIZACION_KEY; import static com.autentia.demo.jwt.security.Constants.ISSUER_INFO; import static com.autentia.demo.jwt.security.Constants.SUPER_SECRET_KEY; import static com.autentia.demo.jwt.security.Constants.TOKEN_BEARER_PREFIX; import static com.autentia.demo.jwt.security.Constants.TOKEN_EXPIRATION_TIME; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.autentia.demo.jwt.usuario.Usuario; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { Usuario credenciales = new ObjectMapper().readValue(request.getInputStream(), Usuario.class); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( credenciales.getUsername(), credenciales.getPassword(), new ArrayList<>())); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException { String token = Jwts.builder().setIssuedAt(new Date()).setIssuer(ISSUER_INFO) .setSubject(((User)auth.getPrincipal()).getUsername()) .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SUPER_SECRET_KEY).compact(); response.addHeader(HEADER_AUTHORIZACION_KEY, TOKEN_BEARER_PREFIX + " " + token); } }
No hay obligación de devolver el token en la cabecera ni con una clave concreta pero se recomienda seguir los estándares utilizados en la actualidad (RFC 2616, RFC 6750). Lo habitual es devolverlo en la cabecera HTTP utilizando la clave “Authorization” e indicando que el valor es un token “Bearer “ + token
Este token lo deberá conservar vuestro cliente web en su localstorage y remitirlo en las peticiones posteriores que se hagan al API.
6.2. Autorización
La clase responsable de la autorización verifica la cabecera en busca de un token, se verifica el token y se extrae la información del mismo para establecer la identidad del usuario dentro del contexto de seguridad de la aplicación. No se requieren accesos adicionales a BD ya que al estar firmado digitalmente si hay alguna alteración en el token se corrompe.
JWTAuthenticationFilter.java
package com.autentia.demo.jwt.security; import static com.autentia.demo.jwt.security.Constants.HEADER_AUTHORIZACION_KEY; import static com.autentia.demo.jwt.security.Constants.SUPER_SECRET_KEY; import static com.autentia.demo.jwt.security.Constants.TOKEN_BEARER_PREFIX; import java.io.IOException; import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import io.jsonwebtoken.Jwts; public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authManager) { super(authManager); } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_AUTHORIZACION_KEY); if (header == null || !header.startsWith(TOKEN_BEARER_PREFIX)) { chain.doFilter(req, res); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(req); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(req, res); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader(HEADER_AUTHORIZACION_KEY); if (token != null) { // Se procesa el token y se recupera el usuario. String user = Jwts.parser() .setSigningKey(SUPER_SECRET_KEY) .parseClaimsJws(token.replace(TOKEN_BEARER_PREFIX, "")) .getBody() .getSubject(); if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); } return null; } return null; } }
7. Probando…probando
Para las pruebas seguiremos el flujo que vimos previamente, primero, se invoca al login para recuperar el token y posteriormente invocaremos las llamadas al API utilizando el token obtenido.
#Se lanza una petición de login curl -i -H "Content-Type: application/json" -X POST -d '{ "username": "admin", "password": "password"}' http://localhost:8080/login # Recuperamos los usuarios dados de alta curl -H "Authorization: Bearer xxx.yyy.zzz" http://localhost:8080/users/ # Damos de alta un nuevo usuario curl -i -H 'Content-Type: application/json' -H 'Authorization: Bearer xxx.yyy.zzz' -X POST -d '{ "username": "daenerys", "password": "dracarys"}' http://localhost:8080/users/
Una vez arrancamos la aplicación y lanzamos los comandos obtendremos algo parecido a la imagen adjunta. Si intentamos invocar alguna URL sin el token obtendremos un código de error HTTP 403.
8. Conclusiones
La securización de API es un tema muy extenso y hemos visto una pequeña parte en este tutorial. Como habréis podido observar Spring nos facilita la incorporación de JWT a nuestras APIs gracias a su “magia”. En el caso que no dispongáis de esta posibilidad, os animo a consultar los frameworks y librerías existentes ya que cada vez está más extendido el uso de los tokens para diferentes lenguajes.
Espero que os haya servido
9. Referencias
- Código fuente completo en github.
- Documentación y librerías sobre JWT.
- Tutorial sobre securización de un API REST con node.js y JWT.
- Tutorial de Natalia Roales sobre Spring Boot.
- Documentación sobre Spring Boot.
- Documentación sobre Spring Security.
- Documentación sobre Cross-origin resource sharing (CORS).
- Documentación sobre Cross-Site Request Forgery.
Una pregunta, y si yo quiero levantar usuarios de la base de datos con sus respectivos roles, como hago?
Saludos
Buenas,
La librería utilizada jjwt y Spring security puede proporcionarte toda la funcionalidad que necesitas. Aunque el tema puede dar para otro tutorial, a grandes rasgos, tienes que recuperar de la BD los roles del usuario e incorporarlo al contexto de seguridad de Spring Security. En la clase UsuarioDetailsServiceImpl al devolver los datos del usuario podemos incorporar una lista con las autorizaciones del usuario (GrantedAuthority).
Una vez se disponga de las autorizaciones podemos incluir en el token que se entrega al usuario, la lista de roles para que vuestro frontal se comporte conforme a estas autorizaciones.Como mencioné anteriormente podemos personalizar nuestros tokens e incluir la información que se desee, por ejemplo en un campo «roles» e incluir una lista con los nombres de los roles asociados del usuario, consulta los métodos de la clase Jwts para incluir más campos.
Por ultimo en el lado de vuestra API REST, al recibir las peticiones securizadas de los usuarios, durante el parseo del token asociado se puede recuperar del token la lista de autorizaciones. Esto evita consultar nuevamente los permisos del usuario en la BD.
Espero haber respondido a tu duda,
Un saludo
Hola, me parecio interesante todo pero mi duda esta si me la puedes aclarar por favor, mencionaste que debemos guardar el jwt en el localstorage, pero como puedo guardar el jwt en esa memoria desde la clase java, ya que solo puedo acceder a localStorage desde javascript.
Y luego, al realizar alguna peticion tendra que ser todas por ajax para enviar con el jwt? porque, como digo en el parrafo anterior, una clase java no tiene acceso al localStorage, solo el javascript.
Buenas,
El API que se ha desarrollado en el ejemplo se centra únicamente en el lado del servidor. Podéis desarrollar el frontal web o vuestro cliente web en cualquier lenguaje, únicamente deben remitir correctamente el token generado al API. Hago referencia al localstorage por dar ideas y ser un mecanismo muy utilizado para almacenar este tipo de tokens pero dependiendo del cliente que se utilice se debe utilizar el mecanismo adecuado.
Un saludo
Hola, me gusta mucho el tema y estoy desarrollando un proyecto usando json web tokens. Me preguntaba si es posible, desde la parte del cliente, editar solo la parte del payload del token (sin saber la clave) y remitir el token con esa parte cambiada al API y que sea valido.
En mi caso me gustaría almacenar el tipo de rol del usuario en el token, para así cuando el usuario cliente me lo devuelva puedo comprobar su rol y darle acceso a determinadas acciones o no. Pero claro, en caso que se puede editar el payload desde el cliente, sin modificar la firma, no sería seguro porque cualquiera puede editar su rol dentro del token.
Un saludo
Buenas,
No me queda muy claro lo que intentas montar pero suena extraño que desde el lado cliente se indique el rol que se tiene en la aplicación.
Piensa en el token como en un ticket de aparcamiento, por mucho que lo modifique a mano, la hora de entrada la sabe el sistema, lo mismo pasa con los roles. Si el token no está alterado o expirado, en el lado servidor no hace falta volver a consultar los roles nuevamente.
La gracia de indicarte el rol en el token es que el lado cliente puede personalizar la interfaz en función de los datos del token.
Espero que te sirva de ayuda.
Un saludo.
Buenas, tengo una duda, si interceptaran la petición GET o POST, podrían coger el token del campo Authorization y realizar todas las consultas posibles con ese token no es asi? hasta que dejara de ser valido si tiene un tiempo de expiracion, hay alguna forma de solucionarlo si fuera cierto?
Un saludo
Buenas,
es posible que una peticion http sea parada intercepte el token y posteriormente nos realicen consultas a nuestra api con ese token?
Un saludo
Buenas,
Podrían utilizarlo mientras el token esté vigente, no deja de ser tráfico http «en plano».
Podrías cifrar el JWT pero no suele ser lo normal, es preferible cifrar todo el tráfico del servidor utilizando SSL para evitar que puedan el leer todo el contenido (incluida las cabeceras).
Espero que te sirva de ayuda.
Un saludo
Trato de implementar el registro de usuarios agregando otra ruta que no pase por el filtro (igual que /login) pero igual me redirecciona a /login.
1. ¿Qué podría estar haciendo mal?
2. ¿Cómo puedo hacer que las respuestas no sean html ?
Saludos.
Buenas,
Acerca de tu primera pregunta, hay que ajustar el filtro de Spring Security (revisa la clase WebSecurity.java para incluir las rutas que no se validan). Como las combinaciones son infinitas te recomiendo que revises la documentación de Spring Security.
Sobre la segunda, no me queda muy claro. El tutorial explica el uso de JWT en un API REST (Json), si quieres incluir otros contenidos revisa la documentación de Spring MVC donde te detallan las diferentes posibilidades.
Espero que te sirva.
Un saludo
Hola, exelente tutorial; solo tengo una duda, cómo mapeas la ruta htt://localhost:8080/login.
He revisado todo el código y no encuentro dicha configuración, disculpa si la pregunta es trivial pero soy nuevo en Spring.
Saludos.
Buenas,
Entiendo que estás intentando generar contenido HTML utilizando Spring MVC. Dependiendo de lo que quieras montar puede cambiar mucho. Te recomiendo que revises la documentación de Spring MVC y Spring Security.
Spring dispone de ejemplos en github, adjunto el enlace donde puedes ver diferentes configuraciones
https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples
Espero que te sirva
Un saludo.
Hola excelente articulo, me quedan algunas dudas y no se si se pueda hacer, entiendo que cuando realizas este paso source.registerCorsConfiguration(«/**», new CorsConfiguration().applyPermitDefaultValues()); solo se van aceptar peticiones POST, GET y HEAD, pero que pasa si quiero aceptar PUT y DELETE?,
En efecto, en el ejemplo no se requiere el resto de métodos. Consulta la documentación de Spring para ajustarlo a tus necesidades:
https://docs.spring.io/spring-security/site/docs/current/reference/html/cors.html
Espero que te sirva
Un saludo
Hola, tengo un error de 403 Forbbiden al enviar las credenciales por PostMan a la ruta localhost:8080/login, implementé el código tal y aparece en el tutorial, que podría ser?
La URL de login es quien te proporciona el token con el que puedes realizar el resto de llamadas. Si las credenciales fueran incorrectas devolvería un 401 (no autorizado) y no un 403.
Suena a que la configuración de tu aplicación es incorrecta.
Descargate el código fuente de github y compáralo con tu implementación.
Espero que te sirva de ayuda
Buenas, muy buen artículo, una pregunta con respecto al mapeo de localhost:8080/login, hay que implementar para esto un @RestController, o se configura por defecto de alguna forma.
Saludos desde Cuba
Buenas,
No hace falta, los campos que interesan (usuario, password) los recupera la clase JWTAuthenticationFilter. En la configuración de la seguridad (WebSecurity) se indica que la URL correspondiente al login no incluye token porque obviamente a través del login se te genera el token.
Espero que te sirva,
Un saludo
Hola,
de antemano gracias por el tutorial. Me gustaría aplicarle el tema de roles. En el método loadUserByUsername ya estoy cargando los roles que tengo definidos y estoy usando @PreAuthorize en los endpoints para restringir el acceso. Sin embargo con la sola autenticación se accede a los endpoints, como puedo restringir la autorización?
Buenas,
La securización es un tema amplio y depende de las necesidades que tenga tu aplicación. Lo mejor es que revises la documentación de spring-security, está muy bien documentada y dispone de múltiples ejemplos.
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/
Espero que te sirva de ayuda,
Un saludo
Hola, qué tal? en la clase JWTAuthenticationFilter, tengo un error de compilación, me extraña porque copie el mismo código del repositorio de github.
Usuario credenciales = new ObjectMapper().readValue(request.getInputStream(), Usuario.class);
Esta línea me da el error. Dice lo siguiente el compilador: The type com.fasterxml.jackson.core.JsonParser cannot be resolved. It is indirectly referenced from required .class files.
Gracias por responder. Saludos.
Buenas,
Según indicas a tu proyecto le falta alguna librería. Revisa la configuración de Maven, es posible que tu repositorio esté corrupto (directorio .m2) y no pueda acceder a alguna librería.
Espero que te sirva de ayuda,
Un saludo
Que tal, tengo una duda, en que momento declaras el metodo /login en el controller? Ya que yo trato de implementar un login que ya voy y valido en mi base de datos si exixte mi usuario pero no se en que momento se inicializan las clases para poder generar el token. Saludos desde CDMX.
Buenas,
En esta demostración no hace falta declararlo en el controlador, se define en el fichero WebSecurity que la URL terminada en «/login» no requiera credenciales (token) puesto que en esta petición se suministra las credenciales iniciales con las que se genera el token (observa el diagrama).
Los filtros que se indican en el tutorial se encargan de la autenticación y de la autorización (es configurable). Se basa en Spring Security, dependiendo de las necesidades de tu negocio la implementación de la seguridad varía.
La documentación de Spring Security ofrece mucha información sobre las diferentes posibilidades.
Espero que te sirva,
Un saludo
en que momento utiliza la base de datos ?? donde inicializa on los querys de import.sql ??
Buenas,
Spring Boot detecta que se trata de una base de datos en memoria y por convención al arrancar la aplicación lanza el script import.sql para inicializar la BD. Es parte de la magia de Spring ya que te evita tener que configurar lo mismo en todos tus proyectos.
En la documentación de Spring tienes más detalles.
Espero que te sirva,
Un saludo
Hola, se supone que el usuario admin ya debe estar guardado en la base de datos ?
Buenas,
Para hacer la demostración del uso de tokens he incluido un usuario cualquiera (en este caso admin). En este ejemplo NO se han incluido roles, permisos o similar, es un nombre como otro cualquiera.
Espero que te sirva,
Un saludo
Hola una ayuda para quienes usamos windows 10 al enviar la petición con curl hay que realizar un pequeño cambio en el comando que tiene que ver con las comillas dobles espero les sirva.
curl -i -H «Content-Type: application/json» -X POST -d «{ \»username\»: \»admin\», \»password\»: \»password\»}» http://localhost:8080/login
Saludos.
new ObjectMapper().readValue(request.getInputStream(), Usuario.class); Aqui hay un grave problema de seguridad readValue
Buenas Sergio,
Es interesante tu comentario ya que os recomiendo que utilicéis versiones actualizadas de las librerías y reviséis todo el código puesto que continuamente surgen mejoras en librerías, vulnerabilidades..etc. que os pueden afectar.
En este caso, en 2018, el NCC Group detectó vulnerabilidades en la librería Jackson.
Un saludo.
Hola buenas! tengo que implementar un login pero que autentique en base a una api externa, tengo la conexion mediante feign client pero no se como hacer que el login autentique contra esta, podrias orientarme un poco en esto?
Al tratarse de algo específico de la integración que estás realizando, poco puedo ayudar. Spring tiene un documentación excelente y allí dispones del mejor detalle.
Un saludo
No entendia de donde salia lo del login, hasta que vi que era automatico. Pero ya pude crear el JWT. No he checado la validación pero debe de funcionar. Ya he usado otros JWT. Lo que mas me encanto es que pudo adaptarlo a mis entidades.
Muchas, muchas, MUCHAS GRACIAS
si el token muere, como se puede refrescar estando dentro de la aplicación?
El refresco se realiza utilizando un token especial llamado refresh token. Suele tener una duración mayor, se genera habitualmente al producirse el login. Cuando el token de acceso está a punto de caducar o ha caducado se envía el refresh token para generar un token de acceso nuevo sin necesidad de autenticarse nuevamente.
Espero que te sirva de ayuda,
Un saludo
una consulta? si usamos JWT no es necesario usar cookies? deonde se aloja el jwt del lado del cliente?
La gracia de los JWT es que no tienes que preocuparte de las cookies, no te hacen falta, el token contiene la información relevante para tu aplicación. En servicios que tengan una alta carga, cualquier servidor puede tomar la petición sin preocuparse de la sesión o cookies. Acerca de la ubicación, pues depende de tus necesidades, ha habido controversia acerca de la seguridad que plantea cada una de las opciones disponibles.
https://dev.to/cotter/localstorage-vs-cookies-all-you-need-to-know-about-storing-jwt-tokens-securely-in-the-front-end-15id
Te recomiendo en base a tus necesidades, optes por la que mejor se adapte.
Espero que te sirva
Un saludo
Estimado, muy buen tutorial.
Una consulta. Como se puede efectuar un request sin tener un token previamente? Es decir, en caso de compartir un link que sea un request de la forma «http://localhost:8080/publicaciones/1» y quisiera recuperar la publicacion «1» sin tener token es posible?
Se pueden especificar las URL que no necesiten token?
Espero ser claro y disculpame si no es correcta mi pregunta.
Muchas gracias, tenia varios días buscando la solución