
"Spring Security를 단순 설정이 아닌 구조로 이해해보기"
Spring 기반의 웹 애플리케이션을 개발하다 보면 어느 순간 보안이라는 주제를 마주하게 된다.
그리고 대부분은 다음과 같은 방식으로 시작한다.
"Spring Security? 일단 예제 코드 붙여놓고 나중에 이해하자."
처음에는 @EnableWebSecurity나 configure(HttpSecurity http) 같은 코드가 마치 마법처럼 동작하는 것처럼 느껴진다. 실제로는 어떻게 작동하는지 모르지만, 우선 돌아가기만 하면 된다는 생각으로 넘어가게 된다.
그러나 프로젝트가 커지고, 인증 방식이 다양해지며,
역할 기반의 인가 처리나 JWT, OAuth2와 같은 복잡한 기능을 도입하게 되면
Spring Security의 구조를 정확히 이해하지 않고는 더 이상 나아가기 어렵다.
많은 개발자들이 Authentication과 Authorization이라는 용어를 혼용해서 사용한다.
나 또한 처음에는 두 개념이 비슷한 의미라고 생각했다.
| 용어 | 의미 |
|---|---|
| Authentication | 사용자가 누구인지 확인하는 과정 (예: 로그인) |
| Authorization | 사용자가 특정 기능을 수행할 수 있는 권한이 있는지 검증하는 과정 (예: 관리자 페이지 접근) |
Spring Security는 이 두 개념을 명확히 구분하고, 각 단계를 체계적인 구조로 처리한다.
하지만 이 구조는 처음 접했을 때 다소 복잡하게 느껴질 수 있다.
이 글은 Spring Security 공식 문서를 바탕으로,
Servlet 기반 애플리케이션에서 인증(Authentication)과 인가(Authorization)가 어떻게 처리되는지 구조적으로 정리하고자 한다.
설정 방법보다는 처리 흐름과 구조 이해에 초점을 맞춘다.
Spring Security를 처음 접하는 개발자뿐만 아니라,
실제 프로젝트에서 보안 흐름을 커스터마이징하려는 개발자에게도
전체 구조를 한 번쯤은 정리해보는 경험이 도움이 된다고 생각한다.
Spring Security는 Spring 기반 애플리케이션에 보안 기능을 제공하는 프레임워크이다.
단순히 로그인/로그아웃 처리를 넘어서,
인증(Authentication)과 인가(Authorization), 세션 관리, 보안 헤더, CSRF 보호
등 다양한 보안 기능을 지원한다.
Spring Security의 가장 큰 특징은 다음과 같다:
@Secured, @PreAuthorize 등)공식 문서에 따르면, Spring Security는 다음 철학을 기반으로 설계되었다:
“보안은 애플리케이션 전반에 걸쳐 적용되어야 하며, 명확하게 분리된 책임 구조 안에서 유연하게 동작해야 한다.”
이는 단순한 인증/인가 기능의 구현을 넘어,
보안 기능이 전체 애플리케이션 흐름 속에 자연스럽게 녹아들 수 있도록 설계해야 한다는 의미이다.
Spring Security는 크게 두 가지 축으로 나뉜다.
| 구분 | 설명 |
|---|---|
| 인증 (Authentication) | 사용자의 신원을 확인한다. 로그인 처리와 밀접하게 연관된다. |
| 인가 (Authorization) | 인증된 사용자가 어떤 리소스에 접근 가능한지를 판단한다. 권한 체크와 연관된다. |
Spring Security는 이 두 과정을 각각의 컴포넌트로 나누어 처리한다.
이를 통해 보안 흐름을 보다 유연하고 확장 가능하게 구성할 수 있다.
Servlet 기반 Spring 애플리케이션에서 요청이 들어오면
Spring Security는 다음과 같은 흐름으로 동작한다:
UsernamePasswordAuthenticationFilter 등이 동작하여 인증을 수행한다.SecurityContext에 인증 정보를 저장한다.Authentication 객체를 기반으로 접근 권한을 확인한다.이와 같은 구조 덕분에 Spring Security는 단순한 설정을 넘어
실제 서비스 요구에 맞춰 다양한 방식으로 확장할 수 있다.
Spring Security의 핵심 구조는 Filter 기반 보안 처리이다.
Servlet 기반 웹 애플리케이션은 요청과 응답을 Filter Chain을 통해 처리하게 되며,
Spring Security는 이 구조를 바탕으로 보안 기능을 구현한다.
Spring Security는 FilterChainProxy라는 필터를 등록하여,
HTTP 요청이 들어올 때마다 내부적으로 보안 관련 필터들을 순차적으로 호출한다.
이 필터들의 모음이 바로 Security Filter Chain이다.
요청이 들어오면 다음과 같은 흐름으로 처리된다:
DelegatingFilterProxy가 보안 필터 체인을 가로채고 FilterChainProxy가 등록된 보안 필터들을 실행하며 Spring Security의 필터 체인은 일반 서블릿 필터가 아니다.
Servlet 환경에서는 보안 필터를 DelegatingFilterProxy라는 프록시를 통해 등록한다.
FilterChainProxy에 정의되어 있으며,DelegatingFilterProxy는 해당 Bean을 위임(Delegate)하는 역할을 한다.이로 인해 보안 필터 설정을 Spring의 ApplicationContext에서 관리할 수 있게 된다.
하나의 애플리케이션에는 여러 개의 SecurityFilterChain을 둘 수 있으며,
각 체인은 특정 URL 패턴에 대해 별도의 보안 정책을 설정할 수 있다.
예시:
@Bean
public SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**") // 해당 URL만 적용
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
/api/** 경로에 대해서만 인증을 요구하는 필터 체인을 정의한 예시이처럼 Spring Security는 필터 체인을 이용하여
URL 패턴마다 서로 다른 인증/인가 정책을 적용할 수 있는 유연한 구조를 제공한다.
📌 핵심 요약
| 구성 요소 | 역할 |
|---|---|
| DelegatingFilterProxy | Servlet 필터로 등록되며, 실제 보안 로직으로 위임 |
| FilterChainProxy | Spring Security 필터 체인을 관리 |
| SecurityFilterChain | 특정 URL에 대한 보안 필터 그룹 |
인증은 사용자가 누구인지 확인하는 과정이다.
Spring Security는 이 인증 과정을 여러 컴포넌트와 필터를 통해 처리한다.
이 장에서는 인증이 어떻게 동작하는지 전체 흐름부터 핵심 구성 요소,
그리고 커스터마이징 포인트까지 단계별로 살펴본다.
인증 과정은 보통 로그인 요청을 처리할 때 발생하며,
다음과 같은 흐름으로 진행된다:
UsernamePasswordAuthenticationFilter가 요청을 가로챈다.UsernamePasswordAuthenticationToken으로 변환한다.AuthenticationManager로 전달된다.AuthenticationManager는 내부적으로 AuthenticationProvider를 호출한다.AuthenticationProvider는 실제 인증 로직을 수행한다.Authentication 객체가 생성되어 SecurityContext에 저장된다.이후부터는 이 인증 정보를 바탕으로 인가(Authorization)가 수행된다.
Spring Security의 인증 아키텍처는 다음과 같은 주요 컴포넌트들로 구성된다:
/login 경로를 처리하지만, URL은 커스터마이징 가능하다.ProviderManager 구현체가 사용되며,AuthenticationProvider를 순차적으로 검사한다.UserDetails 객체이다.BCryptPasswordEncoder가 기본 구현체로 자주 사용된다.기본 인증 구조 외에도, 다양한 실무 상황에서는 다음과 같은 확장이 필요하다:
이럴 때는 다음과 같은 지점을 커스터마이징하면 된다:
| 확장 포인트 | 커스터마이징 방법 |
|---|---|
| 필터 교체 | UsernamePasswordAuthenticationFilter를 상속하여 재정의 |
| 인증 방식 추가 | 새로운 AuthenticationToken, AuthenticationProvider 구현 |
| 사용자 조회 방식 변경 | UserDetailsService 구현체 수정 |
| 암호화 방식 변경 | PasswordEncoder 교체 |
실제로는 필터를 추가하거나, Security 설정에서 .addFilterBefore()를 활용해
JWT 인증 필터 등을 등록하는 방식이 많이 사용된다.
| 컴포넌트 | 역할 |
|---|---|
| UsernamePasswordAuthenticationFilter | 로그인 요청을 처리하고 토큰을 생성 |
| AuthenticationManager | 인증 요청을 분배 |
| AuthenticationProvider | 실제 인증 로직 수행 |
| UserDetailsService | 사용자 정보 조회 |
| PasswordEncoder | 비밀번호 암호화/검증 |
다음 장에서는 인증이 완료된 사용자가
실제로 리소스에 접근할 수 있도록 제어하는 인가(Authorization) 구조를 살펴본다.
인가(Authorization)는 인증된 사용자가 어떤 리소스에 접근할 수 있는지를 판단하는 과정이다.
Spring Security는 인증 이후에 이 인가 처리를 수행하며,
보통 컨트롤러 진입 직전 또는 메서드 실행 직전에 수행된다.
Spring Security는 인증이 완료된 사용자 정보를
SecurityContext에 저장하고, 이를 통해 인가를 수행한다.
SecurityContextHolder.getContext().getAuthentication()
이 코드를 통해 현재 로그인한 사용자의 정보를 조회할 수 있다.
Authentication 객체에는 사용자의 권한 정보(GrantedAuthority)가 포함되어 있으며, 이 권한을 기준으로 리소스 접근 허용 여부를 판단하게 된다.
인가 로직은 보통 다음의 흐름으로 처리된다:
📌 주요 컴포넌트 설명
| 컴포넌트 | 설명 |
|---|---|
| SecurityContext | 인증된 사용자의 정보를 담고 있는 컨텍스트 |
| AccessDecisionManager | 인가 여부를 결정하는 중심 로직 |
| AccessDecisionVoter | 권한을 기준으로 인가를 “투표” 방식으로 결정하는 객체 |
| ExceptionTranslationFilter | 인가 실패 시 예외를 처리하고 로그인 페이지 또는 에러 응답 반환 |
기본 구현체는 대부분 AffirmativeBased이며,
하나라도 허용되면 전체 인가를 통과하는 방식으로 동작한다.
Spring Security에서는 크게 URL 기반 인가와 메서드 기반 인가로 나눠서 인가 처리를 구성할 수 있다.
1) URL 기반 인가 설정(HttpSecurity)
보안 설정 클래스에서 HttpSecurity를 통해 URL에 대한 권한을 설정할 수 있다.
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
위 예시처럼 URL 경로마다 권한 조건을 지정할 수 있으며,
모든 요청에 대해 인증 여부 또는 역할 기반 제한을 설정할 수 있다.
2) 메서드 기반 인가 (@Secured, @PreAuthorize)
서비스 또는 컨트롤러 계층에서 어노테이션 기반으로 인가 조건을 명시할 수 있다.
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(UUID userId) { ... }
사용 가능한 어노테이션은 다음과 같다:
| 어노테이션 | 설명 |
|---|---|
| @Secured("ROLE_ADMIN") | 단순한 역할 기반 체크 (접두사 ROLE_ 필수) |
| @PreAuthorize("...") | SpEL을 이용한 조건 지정 (메서드 실행 전 검사) |
| @PostAuthorize("...") | 메서드 실행 후 검사 |
| @RolesAllowed("ADMIN") | JSR-250 기반 권한 체크 |
이를 사용하려면 @EnableGlobalMethodSecurity 또는 Spring Boot 3.0 이상에서는 @EnableMethodSecurity 어노테이션을 활성화해야 한다.
✅ 핵심 요약
| 구분 | 설명 |
|---|---|
| 인가 정보 저장 위치 | SecurityContext 내 Authentication.getAuthorities() |
| 인가 처리 구조 | AccessDecisionManager → AccessDecisionVoter 투표 방식 |
| 주요 방식 | URL 기반 설정, 메서드 기반 어노테이션 |
인증(Authentication)과 인가(Authorization) 두 구조가 전체 요청 흐름 상에서 어떻게 연결되는지, 그리고 실무에서 혼동하기 쉬운 지점이 무엇인지 정리한다.
클라이언트 요청
↓
[인증 처리 필터]
UsernamePasswordAuthenticationFilter
↓
AuthenticationManager → AuthenticationProvider
↓
인증 성공 → SecurityContext에 저장
↓
[인가 처리 필터]
FilterSecurityInterceptor
↓
AccessDecisionManager → AccessDecisionVoter
↓
인가 성공 시 컨트롤러 진입
인증과 인가 비교 요약
| 항목 | 인증 (Authentication) | 인가 (Authorization) |
|---|---|---|
| 목적 | 사용자가 누구인지 확인 | 사용자가 특정 리소스에 접근 가능한지 확인 |
| 시점 | 로그인 시 1회 수행 | 로그인 이후 모든 요청마다 반복 수행 |
| 주요 컴포넌트 | AuthenticationManager, AuthenticationProvider, UserDetailsService | AccessDecisionManager, AccessDecisionVoter |
| 정보 저장 위치 | SecurityContextHolder (Authentication 객체 내부) | 동일 (Authentication.getAuthorities()) |
| 실패 시 결과 | 401 Unauthorized 또는 로그인 페이지 리다이렉트 | 403 Forbidden 또는 AccessDeniedException 발생 |
⚠️ 실무에서 자주 헷갈리는 지점
UsernamePasswordAuthemticationFilter, 인가 필터는 FilterSecurityInterceptor가 사용된다.Spring Security는 단순한 인증/인가 프레임워크를 넘어,
웹 애플리케이션 보안의 전반적인 구조를 설계할 수 있도록 돕는 강력한 도구이다.
이번 글에서 정리한 핵심 요약
UsernamePasswordAuthenticationFilter,AuthenticationManager, AuthenticationProvider 등이 사용된다.SecurityContext의 권한 정보와AccessDecisionManager, AccessDecisionVoter를 통해 처리된다.Spring Blog - Spring Security without WebSecurityConfigurerAdapter
Baeldung - Spring Security Authentication
Baeldung - Spring Security Authorization