본문 바로가기
Framework/Spring Boot

[Spring Boot] Spring Security 1편: 거대한 철문, 폼 로그인(Form Login)과 아키텍처의 이해

반응형

웹 서비스를 운영하다 보면 "로그인한 회원만 글을 쓸 수 있게 해라", "관리자(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 인증 실무 구현 가이드에 대해 아주 뼈 때리게 파헤쳐 보겠습니다!

반응형