Spring Security 6: Volledige JWT-Authenticatie
Praktische gids om JWT-authenticatie te implementeren met Spring Security 6: configuratie, token-generatie, validatie en best practices voor beveiliging.

Authenticatie met JWT (JSON Web Token) is uitgegroeid tot dé standaard om moderne REST-API's te beveiligen. Spring Security 6 introduceert een functionele configuratieaanpak die de implementatie vereenvoudigt en de beveiliging versterkt. Deze gids loopt het hele proces door, van de initiële configuratie tot het beschermen van de endpoints.
Deze tutorial maakt gebruik van Spring Boot 3.2+ en Spring Security 6.2+. De concepten blijven geldig in latere versies, met kleine syntaxaanpassingen.
JWT-architectuur in Spring Security
JWT-authenticatie steunt op een stateless principe: de server bewaart geen sessie. Elk verzoek bevat een ondertekend token dat de identiteit van de gebruiker bewijst. Deze architectuur maakt horizontaal schalen mogelijk zonder dat sessies tussen instanties gedeeld moeten worden.
De JWT-authenticatieflow valt uiteen in meerdere stappen. De gebruiker authenticeert met zijn credentials, ontvangt een ondertekend JWT-token en stuurt dit token mee bij elk volgend verzoek. De server valideert de handtekening en haalt de gebruikersinformatie uit het token.
// Conceptual representation of the authentication flow
public class JwtAuthenticationFlow {
// 1. Initial authentication: POST /api/auth/login
// → Verify credentials against database
// → Generate signed JWT token
// → Return token to client
// 2. Authenticated requests: GET /api/protected
// → Header: Authorization: Bearer <token>
// → Extract and validate token
// → Create SecurityContext
// → Access protected resource
}Deze aanpak doet sticky-sessionproblemen verdwijnen en vereenvoudigt deployment in gedistribueerde omgevingen.
Maven-afhankelijkheden configureren
Het project vereist de Spring Security-afhankelijkheden en een JWT-bibliotheek. JJWT (Java JWT) biedt een vloeiende en goed onderhouden API om tokens te manipuleren.
<!-- pom.xml -->
<dependencies>
<!-- Spring Security for authentication management -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Web for REST endpoints -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JJWT: token generation and validation -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Data JPA for user persistence -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>De opsplitsing in drie JJWT-modules (api, impl, jackson) volgt het principe van inkapseling: alleen de API is zichtbaar tijdens compilatie, de implementatie blijft een runtime-detail.
Service voor JWT-generatie en -validatie
De JWT-service centraliseert alle token-operaties: generatie, claim-extractie en validatie. Een veilige geheime sleutel ondertekent elk token en garandeert de integriteit ervan.
@Service
public class JwtService {
// Secret key injected from application.yml
@Value("${app.jwt.secret}")
private String secretKey;
// Token validity duration (24 hours by default)
@Value("${app.jwt.expiration:86400000}")
private long jwtExpiration;
// Generates a JWT token for an authenticated user
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
// Generates a token with custom claims
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return Jwts.builder()
.claims(extraClaims) // Additional claims (roles, permissions)
.subject(userDetails.getUsername()) // Principal identifier
.issuedAt(new Date()) // Creation date
.expiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSigningKey(), Jwts.SIG.HS256) // HMAC-SHA256 signature
.compact();
}
// Extracts the username (subject) from the token
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// Extracts a specific claim via an extraction function
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// Validates the token: correct signature and not expired
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
// Checks if the token has expired
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// Extracts the expiration date
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// Parses the token and extracts all claims
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey()) // Verifies the signature
.build()
.parseSignedClaims(token) // Parses the signed token
.getPayload(); // Returns the claims
}
// Generates the signing key from the Base64-encoded secret
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}De geheime sleutel moet voldoende lang zijn (minimaal 256 bits voor HS256) en veilig opgeslagen worden, nooit in de broncode.
Gebruik een omgevingsvariabele of een secrets manager voor de JWT-sleutel. Een gelekte sleutel maakt het mogelijk om geldige tokens voor elke gebruiker te vervalsen.
Configuratie van de User-entiteit
De gebruikersentiteit implementeert UserDetails van Spring Security en sluit zo direct aan op het authenticatiesysteem.
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
// Role stored as enum for type safety
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
// UserDetails implementation: returns user authorities
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
// Username corresponds to email in this implementation
@Override
public String getUsername() {
return email;
}
// Account always active (adapt as needed)
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// Getters and setters omitted for brevity
}public enum Role {
USER, // Standard user
ADMIN // Administrator with extended privileges
}Het enum Role beperkt de mogelijke waarden en vereenvoudigt autorisatiecontroles in SpEL-expressies.
JWT-authenticatiefilter
De JWT-filter onderschept elk verzoek om het token te extraheren en te valideren. Hij plaatst zich in de Spring Security-filterketen vóór de standaard authenticatiefilter.
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
// Retrieve the Authorization header
final String authHeader = request.getHeader("Authorization");
// Check for Bearer prefix
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
// Extract the token (without the "Bearer " prefix)
final String jwt = authHeader.substring(7);
try {
// Extract username from token
final String userEmail = jwtService.extractUsername(jwt);
// Check that user is not already authenticated
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// Load user details from database
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
// Validate token (signature + expiration + user match)
if (jwtService.isTokenValid(jwt, userDetails)) {
// Create authentication object
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
// Add request details
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// Set authentication in security context
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
} catch (ExpiredJwtException e) {
// Token expired: user must re-authenticate
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Token expired");
return;
} catch (JwtException e) {
// Invalid token: incorrect signature or malformed format
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token");
return;
}
// Continue the filter chain
filterChain.doFilter(request, response);
}
}OncePerRequestFilter zorgt ervoor dat de filter slechts één keer per verzoek wordt uitgevoerd, ook bij forward of include.
Klaar om je Spring Boot gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Configuratie van Spring Security 6
Spring Security 6 gebruikt een functionele aanpak met lambda's om de beveiligingsketen te configureren. Deze configuratie legt de toegangsregels vast en integreert de JWT-filter.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Enables @PreAuthorize and @PostAuthorize
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// Disable CSRF since API is stateless (no session cookies)
.csrf(csrf -> csrf.disable())
// Configure authorization rules
.authorizeHttpRequests(auth -> auth
// Public endpoints: authentication and registration
.requestMatchers("/api/auth/**").permitAll()
// API documentation accessible without authentication
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
// Admin endpoints reserved for administrators
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// All other requests require authentication
.anyRequest().authenticated()
)
// Stateless mode: no server-side HTTP session
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// Custom authentication provider
.authenticationProvider(authenticationProvider)
// Insert JWT filter before standard authentication filter
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
// User loading service for Spring Security
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
// Authentication provider with password encoder
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// Authentication manager exposed as bean
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
// BCrypt encoder for secure password hashing
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}CSRF uitschakelen is veilig voor een stateless API, omdat de authenticatie op een expliciete header berust en niet op een cookie die de browser automatisch meestuurt.
Authenticatie-controller
De controller stelt endpoints beschikbaar voor registratie en login. Deze endpoints zijn publiek en geven na een succesvolle authenticatie het JWT-token terug.
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final AuthenticationService authService;
// POST /api/auth/register - Register a new user
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register(
@Valid @RequestBody RegisterRequest request
) {
return ResponseEntity.ok(authService.register(request));
}
// POST /api/auth/login - Login existing user
@PostMapping("/login")
public ResponseEntity<AuthenticationResponse> login(
@Valid @RequestBody AuthenticationRequest request
) {
return ResponseEntity.ok(authService.authenticate(request));
}
// POST /api/auth/refresh - Token refresh (optional)
@PostMapping("/refresh")
public ResponseEntity<AuthenticationResponse> refresh(
@RequestHeader("Authorization") String authHeader
) {
return ResponseEntity.ok(authService.refreshToken(authHeader));
}
}public record AuthenticationRequest(
@NotBlank(message = "Email required")
@Email(message = "Invalid email format")
String email,
@NotBlank(message = "Password required")
String password
) {}public record RegisterRequest(
@NotBlank(message = "First name required")
String firstName,
@NotBlank(message = "Last name required")
String lastName,
@NotBlank(message = "Email required")
@Email(message = "Invalid email format")
String email,
@NotBlank(message = "Password required")
@Size(min = 8, message = "Password must contain at least 8 characters")
String password
) {}public record AuthenticationResponse(
String token,
String type,
long expiresIn
) {
public AuthenticationResponse(String token, long expiresIn) {
this(token, "Bearer", expiresIn);
}
}Java-records vereenvoudigen de definitie van DTO's en garanderen meteen onveranderlijkheid.
Authenticatieservice
De service orkestreert de logica voor registratie en authenticatie en delegeert de tokengeneratie aan JwtService.
@Service
@RequiredArgsConstructor
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
@Value("${app.jwt.expiration:86400000}")
private long jwtExpiration;
// Register a new user
@Transactional
public AuthenticationResponse register(RegisterRequest request) {
// Check that email is not already in use
if (userRepository.existsByEmail(request.email())) {
throw new EmailAlreadyExistsException("Email already registered");
}
// Create user entity with hashed password
User user = new User();
user.setFirstName(request.firstName());
user.setLastName(request.lastName());
user.setEmail(request.email());
user.setPassword(passwordEncoder.encode(request.password()));
user.setRole(Role.USER);
// Persist the user
userRepository.save(user);
// Generate and return JWT token
String jwtToken = jwtService.generateToken(user);
return new AuthenticationResponse(jwtToken, jwtExpiration);
}
// Authenticate an existing user
public AuthenticationResponse authenticate(AuthenticationRequest request) {
// Delegate verification to AuthenticationManager
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.email(),
request.password()
)
);
// Load user (authentication succeeded)
User user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
// Generate token with additional claims
Map<String, Object> claims = new HashMap<>();
claims.put("role", user.getRole().name());
claims.put("userId", user.getId());
String jwtToken = jwtService.generateToken(claims, user);
return new AuthenticationResponse(jwtToken, jwtExpiration);
}
// Refresh token (extend session)
public AuthenticationResponse refreshToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new InvalidTokenException("Invalid token");
}
String oldToken = authHeader.substring(7);
String userEmail = jwtService.extractUsername(oldToken);
User user = userRepository.findByEmail(userEmail)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
// Generate new token
String newToken = jwtService.generateToken(user);
return new AuthenticationResponse(newToken, jwtExpiration);
}
}AuthenticationManager centraliseert de controle van de credentials en maakt het eenvoudig om van authenticatiestrategie te wisselen.
Voor extra beveiliging is een refresh-tokensysteem aan te raden, met langere geldigheid, opgeslagen in de database en intrekbaar.
Endpoints beschermen met annotaties
Method-level security maakt fijnmazige autorisatiecontrole mogelijk, rechtstreeks in de business-code.
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// Accessible to all authenticated users
@GetMapping("/me")
public ResponseEntity<UserDTO> getCurrentUser(
@AuthenticationPrincipal User currentUser
) {
return ResponseEntity.ok(UserDTO.from(currentUser));
}
// Only the concerned user or an admin can modify the profile
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request
) {
return ResponseEntity.ok(userService.updateUser(id, request));
}
// Reserved for administrators
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ResponseEntity<List<UserDTO>> getAllUsers() {
return ResponseEntity.ok(userService.findAll());
}
// Deletion with post-execution verification
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')") // All methods require ADMIN
public class AdminController {
private final AdminService adminService;
@GetMapping("/dashboard")
public ResponseEntity<DashboardDTO> getDashboard() {
return ResponseEntity.ok(adminService.getDashboardStats());
}
@PostMapping("/users/{id}/role")
public ResponseEntity<UserDTO> changeUserRole(
@PathVariable Long id,
@RequestParam Role newRole
) {
return ResponseEntity.ok(adminService.changeUserRole(id, newRole));
}
}De annotatie @AuthenticationPrincipal injecteert de geauthenticeerde gebruiker rechtstreeks en vermijdt handmatig gebruik van de SecurityContext.
Foutafhandeling van authenticatie
Gecentraliseerde foutafhandeling zorgt voor consistente en duidelijke antwoorden wanneer authenticatie mislukt.
@RestControllerAdvice
public class SecurityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(SecurityExceptionHandler.class);
// Authentication error (incorrect credentials)
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleBadCredentials(BadCredentialsException ex) {
return new ErrorResponse(
"INVALID_CREDENTIALS",
"Invalid email or password",
null
);
}
// Access denied (authenticated but not authorized)
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAccessDenied(AccessDeniedException ex) {
return new ErrorResponse(
"ACCESS_DENIED",
"Access to this resource is not authorized",
null
);
}
// Expired JWT token
@ExceptionHandler(ExpiredJwtException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleExpiredToken(ExpiredJwtException ex) {
return new ErrorResponse(
"TOKEN_EXPIRED",
"Session expired, please log in again",
null
);
}
// Invalid JWT token
@ExceptionHandler(JwtException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleInvalidToken(JwtException ex) {
log.warn("Invalid JWT token: {}", ex.getMessage());
return new ErrorResponse(
"INVALID_TOKEN",
"Invalid authentication token",
null
);
}
// Email already in use during registration
@ExceptionHandler(EmailAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleEmailExists(EmailAlreadyExistsException ex) {
return new ErrorResponse(
"EMAIL_EXISTS",
ex.getMessage(),
null
);
}
}public record ErrorResponse(
String code,
String message,
Map<String, String> details
) {}Gestandaardiseerde foutcodes vereenvoudigen de verwerking aan client-zijde en het debuggen.
Configuratie van application.yml
Geëxternaliseerde configuratie maakt het mogelijk om de JWT-parameters per omgeving aan te passen.
# application.yml
app:
jwt:
# Base64 secret key (256 bits minimum for HS256)
# Generate with: openssl rand -base64 32
secret: ${JWT_SECRET:yourSuperSecretKeyOf256BitsMinimum}
# Validity duration in milliseconds (24 hours)
expiration: 86400000
spring:
datasource:
url: jdbc:postgresql://localhost:5432/springjwt
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
jpa:
hibernate:
ddl-auto: validate
show-sql: false# application-dev.yml
app:
jwt:
# Short expiration for development (1 hour)
expiration: 3600000
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update
logging:
level:
org.springframework.security: DEBUGIn productie hoort de geheime sleutel uit een omgevingsvariabele of een secret-managementdienst zoals Vault te komen.
Integratietests
De tests verifiëren het volledige gedrag van het authenticatiesysteem, van registratie tot toegang tot beschermde resources.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = Replace.ANY)
class AuthenticationIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldRegisterAndAuthenticateUser() {
// Registration
RegisterRequest registerRequest = new RegisterRequest(
"John", "Doe", "john@example.com", "password123"
);
ResponseEntity<AuthenticationResponse> registerResponse = restTemplate
.postForEntity("/api/auth/register", registerRequest, AuthenticationResponse.class);
assertThat(registerResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(registerResponse.getBody().token()).isNotBlank();
// Login with same credentials
AuthenticationRequest loginRequest = new AuthenticationRequest(
"john@example.com", "password123"
);
ResponseEntity<AuthenticationResponse> loginResponse = restTemplate
.postForEntity("/api/auth/login", loginRequest, AuthenticationResponse.class);
assertThat(loginResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(loginResponse.getBody().token()).isNotBlank();
}
@Test
void shouldRejectInvalidCredentials() {
AuthenticationRequest request = new AuthenticationRequest(
"unknown@example.com", "wrongpassword"
);
ResponseEntity<ErrorResponse> response = restTemplate
.postForEntity("/api/auth/login", request, ErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
void shouldAccessProtectedResourceWithValidToken() {
// Register to get a token
RegisterRequest registerRequest = new RegisterRequest(
"Jane", "Doe", "jane@example.com", "password123"
);
AuthenticationResponse authResponse = restTemplate
.postForObject("/api/auth/register", registerRequest, AuthenticationResponse.class);
// Access protected resource with token
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(authResponse.token());
ResponseEntity<UserDTO> response = restTemplate.exchange(
"/api/users/me",
HttpMethod.GET,
new HttpEntity<>(headers),
UserDTO.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().email()).isEqualTo("jane@example.com");
}
@Test
void shouldRejectAccessWithoutToken() {
ResponseEntity<Void> response = restTemplate
.getForEntity("/api/users/me", Void.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
}Deze tests dekken de belangrijkste scenario's: registratie, login, geautoriseerde toegang en geweigerde toegang.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
De implementatie van JWT-authenticatie met Spring Security 6 volgt een duidelijk patroon. De JWT-service zorgt voor het genereren en valideren van de tokens, de filter onderschept verzoeken om de beveiligingscontext op te bouwen en de configuratie legt de toegangsregels vast.
Deployment-checklist:
- ✅ Geheime sleutel van minimaal 256 bits, opgeslagen in een omgevingsvariabele
- ✅ HTTPS verplicht in productie om tokens onderweg te beschermen
- ✅ Tokengeldigheid afgestemd op de context (15 min tot 24 u)
- ✅ Refresh token voor lange sessies zonder opnieuw in te loggen
- ✅ Gestandaardiseerde foutafhandeling met functionele codes
- ✅ Logging van mislukte authenticatiepogingen
- ✅ Integratietests die de authenticatiescenario's dekken
- ✅ Rate limiting op de authenticatie-endpoints
Deze architectuur biedt een stevige basis om REST-API's te beveiligen en blijft uitbreidbaar voor complexere behoeften zoals multifactor-authenticatie of OAuth2-integratie.
Tags
Delen
Gerelateerde artikelen

Spring Security 6: OAuth2 Resource Server configureren
Praktische gids voor het configureren van een OAuth2 Resource Server met Spring Security 6. JWT-validatie, issuer-configuratie, scope-beheer en Keycloak-integratie.

Spring Modulith: Modulaire Monoliet-architectuur Uitgelegd
Leer Spring Modulith om modulaire monolieten in Java te bouwen. Architectuur, modules, async events en testen met Spring Boot 3 voorbeelden.

Spring Batch 5 Interview: Partitioning, Chunks en Fault Tolerance
Slaag voor Spring Batch 5 interviews: 15 essentiële vragen over partitioning, chunk-verwerking en fault tolerance met Java 21 codevoorbeelden.