Spring Security 6: Vollständige JWT-Authentifizierung
Praxisleitfaden zur Implementierung der JWT-Authentifizierung mit Spring Security 6: Konfiguration, Token-Erzeugung, Validierung und Sicherheits-Best-Practices.

Die Authentifizierung mit JWT (JSON Web Token) hat sich als Standard zur Absicherung moderner REST-APIs durchgesetzt. Spring Security 6 bringt einen funktionalen Konfigurationsansatz mit, der die Implementierung vereinfacht und gleichzeitig die Sicherheit stärkt. Dieser Leitfaden begleitet den gesamten Prozess von der Erstkonfiguration bis zur Absicherung der Endpoints.
Dieses Tutorial verwendet Spring Boot 3.2+ und Spring Security 6.2+. Die Konzepte gelten in späteren Versionen mit kleinen Syntaxanpassungen weiter.
JWT-Architektur in Spring Security
Die JWT-Authentifizierung basiert auf einem Stateless-Prinzip: Der Server speichert keine Sitzung. Jede Anfrage enthält ein signiertes Token, das die Identität des Nutzers belegt. Diese Architektur erlaubt horizontale Skalierung, ohne Sitzungen zwischen Instanzen synchronisieren zu müssen.
Der JWT-Authentifizierungsfluss gliedert sich in mehrere Schritte. Der Nutzer authentifiziert sich mit seinen Anmeldedaten, erhält ein signiertes JWT-Token und schickt dieses Token bei jeder weiteren Anfrage mit. Der Server prüft die Signatur und entnimmt die Nutzerinformationen aus dem 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
}Dieser Ansatz beseitigt Sticky-Session-Probleme und erleichtert das Deployment in verteilten Umgebungen.
Konfiguration der Maven-Abhängigkeiten
Das Projekt benötigt die Spring-Security-Abhängigkeiten und eine JWT-Bibliothek. JJWT (Java JWT) bietet eine flüssige, gut gepflegte API zur Token-Manipulation.
<!-- 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>Die Aufteilung in drei JJWT-Module (api, impl, jackson) folgt dem Kapselungsprinzip: Nur die API ist zur Compile-Zeit sichtbar, die Implementierung bleibt ein Detail zur Laufzeit.
Service zur JWT-Erzeugung und -Validierung
Der JWT-Service bündelt alle Token-Operationen: Erzeugung, Extraktion der Claims und Validierung. Ein sicherer Schlüssel signiert jedes Token und sichert dessen Integrität.
@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);
}
}Der geheime Schlüssel muss ausreichend lang (mindestens 256 Bit für HS256) und sicher abgelegt sein – niemals im Quellcode.
Für den JWT-Schlüssel sollte eine Umgebungsvariable oder ein Secret-Manager verwendet werden. Ein kompromittierter Schlüssel erlaubt es, gültige Tokens für beliebige Nutzer zu fälschen.
Konfiguration der User-Entität
Die User-Entität implementiert das UserDetails-Interface von Spring Security und ermöglicht so die direkte Integration in das Authentifizierungssystem.
@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
}Das Enum Role schränkt die möglichen Werte ein und vereinfacht die Berechtigungsprüfung in SpEL-Ausdrücken.
JWT-Authentifizierungsfilter
Der JWT-Filter fängt jede Anfrage ab, um das Token zu extrahieren und zu validieren. Er reiht sich in die Spring-Security-Filterkette vor dem Standard-Authentifizierungsfilter ein.
@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 stellt sicher, dass der Filter pro Anfrage nur einmal ausgeführt wird, selbst bei Forward oder Include.
Bereit für deine Spring Boot-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Spring-Security-6-Konfiguration
Spring Security 6 verwendet einen funktionalen Ansatz mit Lambdas, um die Sicherheitskette zu konfigurieren. Diese Konfiguration definiert die Zugriffsregeln und integriert den 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 zu deaktivieren ist bei einer stateless API unbedenklich, weil die Authentifizierung auf einem expliziten Header beruht und nicht auf einem vom Browser automatisch gesendeten Cookie.
Authentifizierungs-Controller
Der Controller stellt Endpoints für Registrierung und Login bereit. Diese Endpoints sind öffentlich und liefern nach erfolgreicher Authentifizierung das JWT-Token zurück.
@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 vereinfachen die Definition der DTOs und garantieren zugleich Unveränderlichkeit.
Authentifizierungsservice
Der Service orchestriert die Logik für Registrierung und Authentifizierung und delegiert die Token-Erzeugung an den 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);
}
}Der AuthenticationManager bündelt die Prüfung der Zugangsdaten und erlaubt einen einfachen Wechsel der Authentifizierungsstrategie.
Für mehr Sicherheit empfiehlt sich ein Refresh-Token-System mit längerer Gültigkeit, das in der Datenbank abgelegt und widerrufbar ist.
Endpoint-Schutz mit Annotationen
Method-Level-Security ermöglicht eine feingranulare Berechtigungsprüfung direkt im Geschäftscode.
@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));
}
}Die Annotation @AuthenticationPrincipal injiziert den authentifizierten Nutzer direkt und erspart manuellen Zugriff auf den SecurityContext.
Behandlung von Authentifizierungsfehlern
Eine zentrale Fehlerbehandlung sorgt für konsistente und aussagekräftige Antworten bei fehlgeschlagener Authentifizierung.
@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
) {}Standardisierte Fehlercodes erleichtern die Verarbeitung im Client und das Debugging.
Konfiguration in application.yml
Eine ausgelagerte Konfiguration ermöglicht es, die JWT-Parameter pro Umgebung anzupassen.
# 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 Produktion sollte der Geheimschlüssel aus einer Umgebungsvariable oder einem Secret-Management-Dienst wie Vault stammen.
Integrationstests
Die Tests prüfen das vollständige Verhalten des Authentifizierungssystems – von der Registrierung bis zum Zugriff auf geschützte Ressourcen.
@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);
}
}Diese Tests decken die wichtigsten Szenarien ab: Registrierung, Login, autorisierter Zugriff und verweigerter Zugriff.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Fazit
Die Implementierung der JWT-Authentifizierung mit Spring Security 6 folgt einem klar definierten Muster. Der JWT-Service kümmert sich um Erzeugung und Validierung der Tokens, der Filter fängt Anfragen ab, um den Sicherheitskontext aufzubauen, und die Konfiguration legt die Zugriffsregeln fest.
Checkliste für das Deployment:
- ✅ Geheimschlüssel mit mindestens 256 Bit, in einer Umgebungsvariable abgelegt
- ✅ HTTPS in Produktion zwingend, um Tokens auf dem Transportweg zu schützen
- ✅ Token-Gültigkeit dem Kontext angepasst (15 Min. bis 24 Std.)
- ✅ Refresh Token für lange Sitzungen ohne erneute Anmeldung
- ✅ Standardisierte Fehlerbehandlung mit fachlichen Codes
- ✅ Logging fehlgeschlagener Authentifizierungsversuche
- ✅ Integrationstests für die Authentifizierungs-Szenarien
- ✅ Rate Limiting auf den Authentifizierungs-Endpoints
Diese Architektur bietet eine solide Grundlage zur Absicherung von REST-APIs und bleibt erweiterbar für komplexere Anforderungen wie Multi-Faktor-Authentifizierung oder OAuth2-Integration.
Tags
Teilen
Verwandte Artikel

Spring Security 6: OAuth2 Resource Server einrichten
Praktischer Leitfaden zur Konfiguration eines OAuth2 Resource Servers mit Spring Security 6. JWT-Validierung, Issuer-Konfiguration, Scope-Verwaltung und Keycloak-Integration.

Spring Modulith: Modulare Monolith-Architektur erklärt
Spring Modulith lernen, um modulare Monolithen in Java zu bauen. Architektur, Module, asynchrone Events und Tests mit Spring Boot 3.

Spring Batch 5 Interview: Partitioning, Chunks und Fehlertoleranz
Meistern Sie Spring Batch 5 Interviews: 15 essenzielle Fragen zu Partitioning, Chunk-Verarbeitung und Fehlertoleranz mit Java 21 Codebeispielen.