Spring Security 6: Autentikasi JWT Lengkap
Panduan praktis untuk menerapkan autentikasi JWT dengan Spring Security 6: konfigurasi, pembuatan token, validasi, dan praktik keamanan terbaik.

Autentikasi dengan JWT (JSON Web Token) telah menjadi standar untuk mengamankan REST API modern. Spring Security 6 menghadirkan pendekatan konfigurasi fungsional yang menyederhanakan implementasi sekaligus memperkuat keamanan. Panduan ini membahas seluruh proses, mulai dari konfigurasi awal hingga proteksi endpoint.
Tutorial ini menggunakan Spring Boot 3.2+ dan Spring Security 6.2+. Konsepnya tetap berlaku pada versi yang lebih baru dengan sedikit penyesuaian sintaks.
Arsitektur JWT di Spring Security
Autentikasi JWT bertumpu pada prinsip stateless: server tidak menyimpan sesi apa pun. Setiap permintaan membawa token yang ditandatangani untuk membuktikan identitas pengguna. Arsitektur ini memungkinkan penskalaan horizontal tanpa berbagi sesi antar instance.
Alur autentikasi JWT terbagi menjadi beberapa langkah. Pengguna melakukan autentikasi dengan kredensialnya, menerima token JWT yang ditandatangani, lalu menyertakan token tersebut pada setiap permintaan berikutnya. Server memverifikasi tanda tangannya dan mengekstrak informasi pengguna dari 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
}Pendekatan ini menghilangkan masalah sticky session dan menyederhanakan deployment di lingkungan terdistribusi.
Konfigurasi Dependensi Maven
Proyek memerlukan dependensi Spring Security dan sebuah pustaka JWT. JJWT (Java JWT) menyediakan API yang lancar dan terpelihara untuk memanipulasi token.
<!-- 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>Pemisahan menjadi tiga modul JJWT (api, impl, jackson) mengikuti prinsip enkapsulasi: hanya API yang terlihat saat compile, sedangkan implementasi tetap menjadi detail saat runtime.
Layanan Pembuatan dan Validasi JWT
Layanan JWT memusatkan seluruh operasi token: pembuatan, ekstraksi claim, dan validasi. Sebuah kunci rahasia yang aman menandatangani setiap token dan menjamin integritasnya.
@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);
}
}Kunci rahasia harus cukup panjang (minimal 256 bit untuk HS256) dan disimpan dengan aman, tidak pernah berada di kode sumber.
Gunakan variabel lingkungan atau secrets manager untuk kunci JWT. Kunci yang bocor memungkinkan pemalsuan token sah untuk pengguna mana pun.
Konfigurasi Entitas User
Entitas pengguna mengimplementasikan UserDetails dari Spring Security sehingga terintegrasi langsung dengan sistem autentikasi.
@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
}Enum Role membatasi nilai yang mungkin dan menyederhanakan pemeriksaan otorisasi pada ekspresi SpEL.
Filter Autentikasi JWT
Filter JWT mencegat setiap permintaan untuk mengekstrak dan memvalidasi token. Filter ini disisipkan ke dalam rantai filter Spring Security sebelum filter autentikasi standar.
@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 memastikan filter hanya dijalankan satu kali per permintaan, bahkan saat terjadi forward atau include.
Siap menguasai wawancara Spring Boot Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Konfigurasi Spring Security 6
Spring Security 6 menggunakan pendekatan fungsional dengan lambda untuk mengonfigurasi rantai keamanan. Konfigurasi ini menetapkan aturan akses dan menggabungkan filter JWT.
@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();
}
}Menonaktifkan CSRF aman untuk API stateless karena autentikasi mengandalkan header eksplisit, bukan cookie yang dikirim otomatis oleh peramban.
Controller Autentikasi
Controller menyediakan endpoint registrasi dan login. Endpoint ini publik dan mengembalikan token JWT setelah autentikasi berhasil.
@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);
}
}Record pada Java menyederhanakan definisi DTO sekaligus menjamin imutabilitas.
Layanan Autentikasi
Layanan ini mengorkestrasi logika registrasi dan autentikasi serta mendelegasikan pembuatan token kepada 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 memusatkan verifikasi kredensial dan memudahkan pertukaran strategi autentikasi.
Untuk meningkatkan keamanan, sebaiknya tambahkan sistem refresh token dengan masa berlaku lebih panjang, disimpan di basis data, dan dapat dicabut.
Proteksi Endpoint dengan Anotasi
Keamanan pada level metode memungkinkan kontrol otorisasi yang granular langsung di kode bisnis.
@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));
}
}Anotasi @AuthenticationPrincipal menyuntikkan pengguna terautentikasi secara langsung dan menghindari akses manual ke SecurityContext.
Penanganan Error Autentikasi
Penanganan error yang terpusat memastikan respons konsisten dan informatif ketika autentikasi gagal.
@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
) {}Kode error standar memudahkan pemrosesan di sisi klien dan proses debugging.
Konfigurasi application.yml
Konfigurasi yang dieksternalisasi memungkinkan parameter JWT disesuaikan untuk setiap lingkungan.
# 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: DEBUGDi lingkungan produksi, kunci rahasia harus berasal dari variabel lingkungan atau layanan manajemen rahasia seperti Vault.
Pengujian Integrasi
Pengujian memverifikasi keseluruhan perilaku sistem autentikasi, dari registrasi hingga akses ke sumber daya yang dilindungi.
@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);
}
}Pengujian ini meliputi skenario utama: registrasi, login, akses yang diizinkan, dan akses yang ditolak.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Kesimpulan
Implementasi autentikasi JWT dengan Spring Security 6 mengikuti pola yang jelas. Layanan JWT menangani pembuatan dan validasi token, filter mencegat permintaan untuk membentuk konteks keamanan, dan konfigurasi menetapkan aturan akses.
Daftar periksa deployment:
- ✅ Kunci rahasia minimal 256 bit disimpan dalam variabel lingkungan
- ✅ HTTPS wajib di produksi untuk melindungi token saat transit
- ✅ Masa berlaku token disesuaikan dengan konteks (15 menit – 24 jam)
- ✅ Refresh token untuk sesi panjang tanpa autentikasi ulang
- ✅ Penanganan error yang seragam dengan kode bisnis
- ✅ Logging upaya autentikasi yang gagal
- ✅ Pengujian integrasi yang mencakup skenario autentikasi
- ✅ Rate limiting pada endpoint autentikasi
Arsitektur ini memberikan dasar yang kokoh untuk mengamankan REST API dan tetap dapat diperluas untuk kebutuhan yang lebih kompleks seperti autentikasi multifaktor atau integrasi OAuth2.
Tag
Bagikan
Artikel terkait

Spring Security 6: Konfigurasi OAuth2 Resource Server
Panduan praktis untuk mengonfigurasi OAuth2 Resource Server dengan Spring Security 6. Validasi JWT, konfigurasi issuer, manajemen scope, dan integrasi Keycloak.

Spring Modulith: Arsitektur Monolit Modular Dijelaskan
Pelajari Spring Modulith untuk membangun monolit modular di Java. Arsitektur, modul, event asinkron, dan testing dengan contoh Spring Boot 3.

Wawancara Spring Batch 5: Partisi, Chunk, dan Toleransi Kegagalan
Kuasai wawancara Spring Batch 5: 15 pertanyaan penting tentang partisi, pemrosesan chunk, dan toleransi kegagalan dengan contoh kode Java 21.