# Spring Boot Security 설정 패턴 ## 언제 쓰는가 - SPA + JWT 구조의 Spring Boot API 서비스 - JWT / API Key 이중 인증이 필요할 때 ## 핵심 구조 - SecurityFilterChain (Stateless + CSRF 비활성화) - 화이트리스트 방식 엔드포인트 접근 제어 - 커스텀 필터 체인 (API Key → JWT 순서) - CORS 환경변수 주입 ## 코드 예시 ### SecurityConfig ```java @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/**").authenticated() .anyRequest().denyAll() ) .exceptionHandling(ex -> ex.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) .addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } ``` ### API Key 필터 (접두사 + BCrypt) ```java String key = extractApiKey(req); // Bearer xxx 또는 X-API-Key: xxx String prefix = key.substring(0, 8); List candidates = repo.findByKeyPrefixAndIsActiveTrue(prefix); for (ApiKey c : candidates) { if (passwordEncoder.matches(key, c.getKeyHash())) { SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(userId, null, authorities)); break; } } ``` ### CORS (환경변수 기반) ```java @Value("${app.cors.allowed-origins}") private String allowedOrigins; // 쉼표 구분 CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList(allowedOrigins.split(","))); config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-API-Key")); config.setAllowCredentials(true); config.setMaxAge(3600L); ``` ## 주의사항 - `anyRequest().denyAll()` 필수 — 명시하지 않은 경로 전부 차단 - `PasswordEncoder`는 별도 `@Configuration`으로 분리 (순환의존성 방지) - CORS에 와일드카드(`*`) 대신 명시적 헤더 나열 - API Key는 DB에 BCrypt 해시만 저장, 평문 미저장 - Actuator는 `health`, `info`만 노출 - 이중 인증 불필요 시 API Key 필터 제거하고 JWT만 사용 ## 재사용 방법 - SecurityConfig + JwtFilter 복사 → 경로 매칭만 수정 - 이중 인증 필요 시 ApiKeyFilter 추가 - CORS 허용 오리진은 환경변수로 주입