En aquest tema, aprendrem a implementar l'autenticació basada en JSON Web Tokens (JWT) en una aplicació Spring Boot. Els JWT són una manera segura i estàndard de representar reclamacions entre dues parts. Són àmpliament utilitzats per a l'autenticació i l'autorització en aplicacions web.
Objectius
- Entendre què és un JWT i com funciona.
- Configurar Spring Security per utilitzar JWT.
- Crear un servei per generar i validar JWT.
- Implementar filtres de seguretat per gestionar les sol·licituds autenticades.
Què és un JWT?
Un JSON Web Token (JWT) és un estàndard obert (RFC 7519) que defineix una manera compacta i autònoma de transmetre informació de manera segura entre les parts com un objecte JSON. Aquesta informació pot ser verificada i confiada perquè està signada digitalment.
Estructura d'un JWT
Un JWT consta de tres parts separades per punts (.):
- Header (Capçalera): Conté el tipus de token (JWT) i l'algoritme de signatura utilitzat (per exemple, HMAC SHA256).
- Payload (Càrrega útil): Conté les reclamacions. Les reclamacions són declaracions sobre una entitat (normalment, l'usuari) i dades addicionals.
- Signature (Signatura): La signatura és utilitzada per verificar que el remitente del JWT és qui diu ser i per assegurar que el missatge no ha estat alterat.
Exemple d'un JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImV4cCI6MTYwOTI3MjAwMH0.4fP5Zc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8FZc8F## Configuració de Spring Security per utilitzar JWT ### Dependències Maven Primer, necessitem afegir les dependències necessàries al nostre projecte. Afegeix les següents dependències al teu fitxer `pom.xml`:
### Configuració de seguretat Crea una classe de configuració de seguretat que estengui `WebSecurityConfigurerAdapter` i sobreescriu els mètodes necessaris per configurar la seguretat de la teva aplicació.
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
### Creació del servei JWT Crea una classe `JwtTokenProvider` que s'encarregarà de generar i validar els tokens JWT.
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
import java.util.Date;
@Component public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpiration;
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (Exception e) {
return false;
}
}
}
### Implementació del filtre JWT Crea una classe `JwtAuthenticationFilter` que extengui `OncePerRequestFilter` per interceptar les sol·licituds HTTP i validar el token JWT.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = getJwtFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
### Controlador d'autenticació Crea un controlador per gestionar les sol·licituds d'autenticació i generar el token JWT.
import org.springframework.beans.factory.annotation.Autowired; 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.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.bind.annotation.*;
@RestController @RequestMapping("/auth") public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/login")
public String authenticateUser(@RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return tokenProvider.generateToken(userDetails.getUsername());
} catch (AuthenticationException e) {
throw new RuntimeException("Invalid credentials");
}
}
}
### Model de sol·licitud de login Crea una classe `LoginRequest` per encapsular les dades de la sol·licitud de login.
public class LoginRequest { private String username; private String password;
// Getters and setters
}
## Resum En aquesta secció, hem après a implementar l'autenticació basada en JWT en una aplicació Spring Boot. Hem configurat Spring Security per utilitzar JWT, creat un servei per generar i validar tokens, implementat filtres de seguretat per gestionar les sol·licituds autenticades i creat un controlador per gestionar les sol·licituds d'autenticació. Amb aquests coneixements, estàs preparat per protegir la teva aplicació Spring Boot amb autenticació JWT.
Curs de Spring Boot
Mòdul 1: Introducció a Spring Boot
- Què és Spring Boot?
- Configuració del teu entorn de desenvolupament
- Creant la teva primera aplicació Spring Boot
- Entenent l'estructura del projecte Spring Boot
Mòdul 2: Conceptes bàsics de Spring Boot
- Anotacions de Spring Boot
- Injecció de dependències a Spring Boot
- Configuració de Spring Boot
- Propietats de Spring Boot
Mòdul 3: Construint serveis web RESTful
- Introducció als serveis web RESTful
- Creant controladors REST
- Gestionant mètodes HTTP
- Gestió d'excepcions en REST
Mòdul 4: Accés a dades amb Spring Boot
- Introducció a Spring Data JPA
- Configuració de fonts de dades
- Creant entitats JPA
- Utilitzant repositoris de Spring Data
- Mètodes de consulta a Spring Data JPA
Mòdul 5: Seguretat a Spring Boot
- Introducció a Spring Security
- Configuració de Spring Security
- Autenticació i autorització d'usuaris
- Implementant autenticació JWT
Mòdul 6: Proves a Spring Boot
Mòdul 7: Funcions avançades de Spring Boot
Mòdul 8: Desplegant aplicacions Spring Boot
Mòdul 9: Rendiment i monitorització
- Optimització del rendiment
- Monitorització amb Spring Boot Actuator
- Utilitzant Prometheus i Grafana
- Gestió de registres i logs