웹 서비스를 운영하다 보면 "로그인한 회원만 글을 쓸 수 있게 해라", "관리자(Admin) 권한이 있는 사람만 이 페이지에 접근하게 해라"와 같은 요구사항이 쏟아집니다. 초보 개발자들은 컨트롤러(Controller)의 모든 메서드 첫 줄에 if (session.getAttribute("user") == null) throw new Error(); 같은 코드를 복붙하며 지옥을 맛보곤 합니다. 이처럼 중복되는 보안(Security) 검사 로직을 애플리케이션의 핵심 비즈니스 로직과 완벽하게 분리하여, 개발자가 비즈니스에만 집중할 수 있게 해주는 전 세계에서 가장 강력하고 튼튼한 방패가 바로 Spring Security(스프링 시큐리티)입니다. 하지만 그 강력함만큼이나 초기 진입 장벽이 악명 높게 높은 것으로도 유명합니다.
이번 포스팅에서는 "외계어 같았던 스프링 시큐리티의 웅장한 아키텍처!" 필터 체인(Filter Chain)의 원리와 전통적인 세션 기반 폼 로그인(Form Login)의 완벽 구현 가이드에 대하여 다뤄보겠습니다.

🛡️ 1. 필터 체인(Filter Chain): 요청을 검열하는 만리장성
스프링 시큐리티의 동작 원리를 관통하는 가장 핵심적인 키워드는 서블릿 필터(Servlet Filter)입니다. 클라이언트(브라우저)가 /api/boards라는 주소로 요청을 쏘면, 이 요청은 곧바로 스프링의 @Controller로 가지 않습니다. 컨트롤러 앞에 쳐져 있는 거대한 그물망, 즉 10~15개 정도의 보안 필터들이 한 줄로 이어져 있는 '필터 체인'을 순서대로 거치게 됩니다.

대표적으로 UsernamePasswordAuthenticationFilter는 폼 로그인을 가로채서 ID/PW가 맞는지 검사하고, FilterSecurityInterceptor는 "이 녀석이 이 페이지에 들어갈 권한(Role)이 있는가?"를 마지막으로 검사합니다. 통과하지 못하면 401(미인증) 또는 403(권한없음) 에러를 뱉어내며 요청을 쫓아내 버립니다. 이 덕분에 컨트롤러에는 보안 코드가 단 한 줄도 들어갈 필요가 없어집니다!
🔑 2. 인증(Authentication)과 인가(Authorization)의 명확한 차이
시큐리티를 배울 때 가장 헷갈리는 두 단어를 확실히 정리해야 합니다.
- 인증 (Authentication - "너 누구냐?"): 신원을 확인하는 과정입니다. 아이디와 비밀번호를 입력하고 "나 홍길동이야!"라고 증명하는 로그인 과정이 바로 인증입니다.
- 인가 (Authorization - "너 여기 들어갈 자격 있어?"): 인증이 완료된 후의 단계입니다. 홍길동이 로그인은 했지만, VVIP 회원 전용 페이지에 들어가려고 할 때 "너 일반 회원(USER)이잖아? 여긴 관리자(ADMIN)만 들어와!"라고 막아버리는 권한 검사 과정입니다.
💻 3. Spring Boot 3.x Security 실무 설정 코드 (SecurityFilterChain)
과거 Spring Boot 2.x 시절에는 WebSecurityConfigurerAdapter를 상속받아 세팅했지만, 3.x부터는 이 클래스가 완전히 폐기(Deprecated)되고 컴포넌트 기반의 빈(Bean) 등록 방식으로 전면 개편되었습니다. 아주 깔끔해진 현대의 설정 코드를 봅시다.
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터 체인을 활성화하는 핵심 마법
public class SecurityConfig {
// 1. 단방향 비밀번호 암호화 인코더 빈 등록 (회원가입 시 무조건 이걸로 암호화해서 DB에 넣어야 함!)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 2. 가장 중요한 보안 룰(Rule) 세팅
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 최신 람다식(Lambda) 문법 사용 강제 (Boot 3.x 특징)
.csrf(csrf -> csrf.disable()) // 보통 REST API 서버를 만들 땐 CSRF 방어를 끄는 것이 편함
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**", "/login", "/signup").permitAll() // 이 주소들은 아무나 통과!
.requestMatchers("/api/admin/**").hasRole("ADMIN") // 여기는 관리자 권한 필수!
.anyRequest().authenticated() // 그 외의 모든 쩌리(?) 요청들은 일단 로그인은 하고 와라!
)
.formLogin(form -> form
.loginPage("/login") // 우리가 만든 예쁜 커스텀 로그인 HTML 페이지 주소 지정
.loginProcessingUrl("/perform_login") // 프론트가 form action="/perform_login"으로 POST 날리면 시큐리티가 가로챔!
.defaultSuccessUrl("/home") // 로그인 성공하면 갈 곳
)
.logout(logout -> logout
.logoutUrl("/logout") // 로그아웃 요청 주소
.logoutSuccessUrl("/login?logout") // 로그아웃 성공 시 갈 곳
.invalidateHttpSession(true) // 로그아웃하면 세션 메모리 싹 날려라!
);
return http.build();
}
}
🎯 4. 마무리 및 다음 단계
지금까지 지저분한 if문 도배를 끝내고, 컨트롤러에 닿기 전에 거대한 필터 체인(Filter Chain) 방어막을 쳐서 완벽하게 '인증(로그인)'과 '인가(권한 검사)'를 수행해 내는 Spring Boot 3.x 기반의 Spring Security 폼 로그인(Form Login) 아키텍처에 대해 다루어 보았습니다. 이제 웹 페이지(HTML)를 그리는 전통적인 방식의 보안은 끄떡없습니다.
하지만 치명적인 문제가 있습니다. 현대의 서비스는 서버에서 HTML을 그리지 않고, React, Vue 프론트엔드나 iOS, Android 모바일 앱과 통신하는 'REST API' 서버로 동작합니다. 모바일 앱 환경에서는 폼 로그인의 핵심인 '쿠키(Cookie)'와 '세션(Session)'을 유지하기가 극도로 까다로우며, 서버를 여러 대 두었을 때 세션 정보가 찢어지는 현상도 발생합니다. 이 무거운 상태(Stateful) 기반의 세션을 던져버리고, 가벼운 토큰(Token) 하나로 만사를 해결하는 완벽한 무상태(Stateless) 보안 아키텍처로 넘어가야 할 때입니다. 이어지는 15단계 포스팅에서는 "현대 백엔드 인증의 압도적 절대 표준!" Spring Security와 JWT(JSON Web Token)를 결합한 극강의 REST API 인증 실무 구현 가이드에 대해 아주 뼈 때리게 파헤쳐 보겠습니다!
'Framework > Spring Boot' 카테고리의 다른 글
| [Spring Boot] OAuth 2.0 소셜 로그인: 카카오, 네이버, 구글 연동의 정석 (0) | 2026.07.27 |
|---|---|
| [Spring Boot] Spring Security 2편: 현대 API 인증의 표준, JWT(JSON Web Token) 완벽 구현 (0) | 2026.07.27 |
| [Spring Boot] Redis 캐싱(Caching): DB 부하를 박살내고 응답 속도 10배 끌어올리기 (0) | 2026.07.27 |
| [Spring Boot] 커넥션 풀(HikariCP) 튜닝: 병목을 뚫고 장애를 예방하는 인프라의 심장 (0) | 2026.07.27 |
| [Spring Boot] QueryDSL 입문: 타입 세이프(Type-safe)한 동적 쿼리의 끝판왕 (0) | 2026.07.27 |