Understanding Token-Based Authentication

박진석·2025년 2월 6일
post-thumbnail

Understanding Token-Based Authentication

The Fundamental Concept

Think of token-based authentication like a festival pass. When we first buy our ticket (log in), instead of the festival keeping track of all active visitors in their system, they give us a special tamper-proof wristband (the token). This wristband contains all the information needed: our identity, what exhibits we can access, and when the pass expires.

Core Characteristics

1. Statelessness

// Traditional session-based approach (stateful)
@Service
public class SessionAuthService {
    private final SessionStore sessionStore;  // Server needs to maintain state
    
    public boolean validateSession(String sessionId) {
        return sessionStore.getSession(sessionId) != null;  // Must check against stored state
    }
}

// Token-based approach (stateless)
@Service
public class TokenAuthService {
    private final String secretKey;
    
    public boolean validateToken(String token) {
        try {
            // All necessary information is in the token itself
            Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
            
            return !isTokenExpired(claims);  // No database lookup needed
        } catch (JwtException e) {
            return false;
        }
    }
}

The beauty of statelessness is that each request is self-contained. The server doesn't need to remember anything about the client's previous requests. This is like the festival not needing to maintain a list of current visitors - they just verify each wristband as people enter different exhibits.

2. Scalability

Because tokens are stateless, they excel in distributed systems.

@Configuration
public class LoadBalancerConfig {
    // With sessions, you'd need this kind of configuration
    @Bean
    public SessionRegistry sessionRegistry() {
        // Complex setup to synchronize sessions across servers
        return new DistributedSessionRegistry(redisTemplate);
    }
}

// With tokens, no special configuration needed!
@Service
public class DistributedAuthService {
    // Any server can validate the token independently
    public boolean validateRequest(String token) {
        return jwtService.validateToken(token);
    }
}

Think of it like having multiple entrances to the festival. With traditional sessions, each guard would need to radio check if a visitor is allowed in. With tokens, each guard can independently verify the wristband.

3. Token Integrity and Security

Tokens use cryptographic signatures to ensure they haven't been tampered with:

@Service
public class JwtSecurityService {
    private final String secretKey;
    
    public String createToken(UserDetails user) {
        return Jwts.builder()
            .setSubject(user.getUsername())
            .claim("roles", user.getAuthorities())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
            // The signature is crucial for integrity
            .signWith(SignatureAlgorithm.HS256, secretKey)
            .compact();
    }
    
    public boolean isTokenValid(String token) {
        try {
            // If the signature is invalid, this will throw an exception
            Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token);
            return true;
        } catch (SignatureException e) {
            // Token has been tampered with
            return false;
        } catch (ExpiredJwtException e) {
            // Token has expired
            return false;
        }
    }
}

This is like having a magic marker ink on the wristband - it's virtually impossible to forge, and any attempt to modify it would be immediately noticeable!!

4. Information Flow

@RestController
public class AuthController {
    @PostMapping("/login")
    public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
        // 1. Validate credentials
        Authentication auth = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getUsername(), 
                request.getPassword()
            )
        );
        
        // 2. Generate token with user information
        String token = jwtService.createToken((UserDetails) auth.getPrincipal());
        
        // 3. Return token to client
        return ResponseEntity.ok(new TokenResponse(token));
    }
    
    @GetMapping("/protected-resource")
    public ResponseEntity<?> getResource(@RequestHeader("Authorization") String authHeader) {
        // 4. Extract and validate token from each request
        String token = authHeader.substring(7);  // Remove "Bearer "
        if (jwtService.validateToken(token)) {
            // 5. Process the request
            return ResponseEntity.ok(resourceService.getResource());
        }
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
}

5. Security Considerations

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())  // Tokens handle CSRF protection
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(
                new JwtAuthenticationFilter(jwtService),
                UsernamePasswordAuthenticationFilter.class
            )
            .build();
    }
}

Takeaways :

  • Tokens should be transmitted securely (HTTPS)
  • Keep tokens reasonably short-lived
  • Store tokens securely on the client side
  • Include only necessary information in the token

0개의 댓글