
쿠키는 단순히 브라우저의 데이터 저장소 역할을 합니다. 다음과 같은 데이터를 저장할 수 있습니다:
Cookie: SESSIONID=abc123...
Cookie: jwt=eyJhbGciOiJIUzI1NiIs...
Cookie: SESSIONID=abc123...
Cookie: jwt=eyJhbGciOiJIUzI1NiIs...
인증 방식의 선택은 "쿠키냐 세션이냐 토큰이냐"의 문제가 아니라:



@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
장점
단점
적합한 상황
장점
단점
적합한 상황
세션 고정 공격 대응
// Spring Security 설정 예시
http.sessionManagement()
.sessionFixation()
.newSession(); // 로그인 시 새로운 세션 생성
세션 타임아웃
http.sessionManagement()
.maximumSessions(1) // 동시 세션 제한
.expiredUrl("/login?expired")
.and()
.sessionFixation().migrateSession()
.invalidSessionUrl("/login?invalid");
세션 ID 보호
토큰 설계
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
토큰 저장
// 취약한 방식
localStorage.setItem('token', jwt);
// 권장 방식
document.cookie = `jwt=${token}; HttpOnly; Secure; SameSite=Strict`;
토큰 갱신 전략
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
String refreshToken = extractRefreshToken(request);
if (jwtUtil.validateRefreshToken(refreshToken)) {
String newAccessToken = jwtUtil.generateNewAccessToken(refreshToken);
return ResponseEntity.ok(new TokenResponse(newAccessToken));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Sticky Session
# nginx 설정 예시
upstream backend {
ip_hash; # 같은 사용자는 같은 서버로
server backend1.example.com;
server backend2.example.com;
}
세션 클러스터링
// Spring Session with Redis
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
페이로드 최소화
// Bad: 너무 많은 정보 포함
claims.put("user", userDetails);
// Good: 필요한 정보만 포함
claims.put("sub", userDetails.getUsername());
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
캐싱 전략
// 자주 사용되는 토큰 검증 결과 캐싱
@Cacheable(value = "tokenCache", key = "#token")
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
쿠키 사용 시
로컬 스토리지 사용 시
세션 방식 선택 시
토큰 방식 선택 시