목록으로
개발

Day 66 - Guardian

·4분 읽기
데브코스백엔드부트캠프프로그래머스

이 글은 2025년 06월 09일 작성된 글입니다.

Spring Security를 프로젝트에 적용하면서 CustomAuthenticationFilter, SecurityContext, JWT 인증, 인가 처리, CORS 설정까지 학습했다. 기존에 직접 구현하던 인증/인가 로직을 Spring Security가 이해할 수 있도록 연결하는 과정에 집중했다.


1. CustomAuthenticationFilter 인증 처리

Spring Security가 JWT와 apiKey를 이해하지 못하기 때문에 필터를 추가하여 먼저 인증을 수행하도록 구현했다.

핵심

  • CustomAuthenticationFilter는 UsernamePasswordAuthenticationFilter 이전에 실행된다.
  • accessToken 또는 apiKey가 존재하면 인증을 수행한다.
  • 로그인하지 않은 요청도 통과시켜야 한다.
  • 인증이 필요한지 여부는 이후 Spring Security가 판단한다.
if (accessToken != null || apiKey != null) {
    // 인증 처리
}

인증 흐름

사용자 요청

CustomAuthenticationFilter

UsernamePasswordAuthenticationFilter

인가 처리

Controller

2. 필터에서 예외 처리

필터는 컨트롤러보다 먼저 실행되기 때문에 ControllerAdvice가 처리할 수 없다.

핵심

  • 필터 내부에서 직접 예외 처리 필요
  • 인증 실패 시 즉시 응답 반환
  • 컨트롤러 진입 전에 요청 차단 가능
try {
    // 인증 처리
} catch (Exception e) {
    response.setStatus(401);
}

3. SecurityContext 이해

Spring Security는 현재 요청 사용자의 정보를 SecurityContext에 저장한다.

핵심

  • 인증 성공 후 사용자 정보 저장
  • 인가 과정에서 권한 체크
  • 요청 종료 시 자동 제거
SecurityContextHolder
    .getContext()
    .setAuthentication(authentication);

동작 순서

요청

인증

SecurityContext 저장

인가

Controller 실행

SecurityContext 제거

4. SecurityUser 도입

Spring Security 기본 User 클래스에는 프로젝트에서 필요한 id, nickname 정보가 없다.

핵심

  • UserDetails 구현
  • id 저장 가능
  • nickname 저장 가능
  • Principal에서 Member 복원 가능
public class SecurityUser implements UserDetails {
    private Long id;
    private String nickname;
}

5. 선언적 인가 처리

기존에 컨트롤러에서 직접 권한을 검사하던 방식을 제거하고 Security 설정으로 이동했다.

핵심

  • 관리자 API 접근 제한
  • 인증 사용자 접근 제한
  • Security 설정에서 일괄 관리
.requestMatchers("/api/*/adm/**")
.hasRole("ADMIN")

URL 패턴

*   → 한 단계
**  → 여러 단계
 
/api/*/adm/**

예시

/api/v1/adm/members
/api/v2/adm/posts/1
/api/test/adm/stats/today

6. 인증된 사용자 복원

CustomAuthenticationFilter가 인증을 끝냈기 때문에 Rq에서 다시 인증할 필요가 없어졌다.

변경 전

Rq.getActor()
    -> 직접 인증

변경 후

SecurityContext
    -> Principal
    -> Member 복원

핵심

  • 인증은 필터에서 1회 수행
  • 컨트롤러는 인증 결과만 사용
  • 중복 제거

7. REST API 보안 설정

REST API에서 사용하지 않는 Security 기능을 비활성화했다.

핵심

  • Form Login 비활성화
  • Session 인증 비활성화
  • HTTP Basic 비활성화
  • Stateless 구조 사용
http
    .formLogin(form -> form.disable())
    .httpBasic(httpBasic -> httpBasic.disable());

8. 쿠키 보안 강화

apiKey 쿠키에 보안 옵션을 추가했다.

핵심

  • HttpOnly
  • Secure
  • SameSite=Strict
  • MaxAge=1년
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setMaxAge(31536000);

SameSite 옵션

옵션설명
None모든 요청 허용
LaxGET 중심 허용
Strict동일 사이트만 허용

9. CORS 설정

프론트엔드와 백엔드가 서로 다른 도메인에서 통신할 수 있도록 설정했다.

핵심

  • 허용 Origin 설정
  • 허용 Header 설정
  • 허용 Method 설정
configuration.addAllowedOrigin("http://localhost:3000");

10. @WithUserDetails 활용

테스트 코드에서 인증 관련 반복 코드를 제거했다.

기존

.header("Authorization", "Bearer ...")

변경

@WithUserDetails("user1")

핵심

  • 테스트 코드 단순화
  • 인증 사용자 자동 생성
  • UserDetailsService 필요

11. 로그 레벨 설정

개발 환경에서는 DEBUG 로그를 사용하도록 설정했다.

로그 레벨

레벨설명
TRACE가장 상세
DEBUG개발용
INFO기본
WARN경고
ERROR오류
logging:
  level:
    com.back: DEBUG

12. 순환참조 문제 해결

SecurityConfig, Filter, MemberService, Rq 사이에서 순환참조가 발생했다.

문제

CustomAuthenticationFilter

Rq

MemberService

SecurityConfig

CustomAuthenticationFilter

해결

  • 의존성 구조 재설계
  • 순환 의존 제거
  • Security 설정 분리

✅ 정리

  • CustomAuthenticationFilter를 추가하여 JWT와 apiKey 인증을 Spring Security 앞단에서 처리했다.
  • 인증 성공 시 SecurityContext에 사용자 정보를 저장하여 Spring Security가 이해할 수 있도록 연결했다.
  • SecurityUser를 도입하여 id와 nickname 같은 사용자 정보를 함께 관리할 수 있게 되었다.
  • 관리자 권한과 인증 여부를 Security 설정으로 이동하여 선언적으로 인가를 처리하게 되었다.
  • REST API 환경에 맞게 Form Login, Session 인증 등을 비활성화하고 Stateless 구조를 구성했다.
  • 쿠키에 Secure, HttpOnly, SameSite 옵션을 적용하여 보안을 강화했다.
  • @WithUserDetails를 사용하여 테스트 코드의 인증 관련 보일러플레이트를 제거했다.
  • Spring Security의 필터 체인 구조와 인증·인가 흐름을 실제 프로젝트에 맞게 커스터마이징하는 방법을 학습했다.
← 목록으로
데브코스백엔드부트캠프프로그래머스