Day 66 - Guardian
이 글은 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
↓
인가 처리
↓
Controller2. 필터에서 예외 처리
필터는 컨트롤러보다 먼저 실행되기 때문에 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/today6. 인증된 사용자 복원
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 | 모든 요청 허용 |
| Lax | GET 중심 허용 |
| 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: DEBUG12. 순환참조 문제 해결
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의 필터 체인 구조와 인증·인가 흐름을 실제 프로젝트에 맞게 커스터마이징하는 방법을 학습했다.