백엔드

Spring Boot Security 설정 패턴

백엔드

Spring Boot Security 설정 패턴 — 실전 적용 구조와 코드 예시

언제 쓰나 · SPA + JWT 구조의 Spring Boot API 서비스 · JWT / API Key 이중 인증이 필요할 때

#backend#Spring#Boot#Security
다운로드
# 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<ApiKey> 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 허용 오리진은 환경변수로 주입
jt · v1 · CC0-1.0 · 복사 0