Spring Security는 자바(Java) 기반의 애플리케이션(특히 Spring 프레임워크)에서 보안(인증 및 인가)을 담당하는 가장 강력하고 표준적인 프레임워크이다.
쉽게 비유하자면, 웹 사이트라는 '건물'을 지킬 때 입구에 배치하는 최첨단 경비 시스템과 같다.
1. 핵심 개념: 인증과 인가
Spring Security를 이해하려면 두 가지 단어를 반드시 구분해야 합니다.
- 인증 (Authentication): "당신은 누구입니까?"
- 사용자의 신원을 확인하는 절차이다.
- 예: 로그인(ID/Password 입력), 지문 인식, OTP 등
- 인가 (Authorization): "당신은 무엇을 할 수 있습니까?"
- 인증된 사용자가 특정 리소스에 접근할 권한이 있는지 확인하는 절차이다.
- 예: 일반 사용자는 내 정보만 볼 수 있고, 관리자 페이지에는 접근할 수 없다.
2. Spring Security의 동작 원리
Spring Security는 기본적으로 서블릿 필터(Servlet Filter) 체인을 기반으로 동작한다. 클라이언트의 요청이 Controller(DispatcherServlet)에 도달하기 전에, 필터들이 가로채서 보안 검사를 수행한다.
- SecurityFilterChain: 여러 보안 필터들이 사슬처럼 연결되어 있습니다. 요청이 들어오면 순서대로 검사를 수행한다.
- DelegatingFilterProxy: 톰캣 같은 서블릿 컨테이너와 스프링 컨테이너 사이를 연결해 주는 다리 역할을 합니다.
- SecurityContextHolder: 인증이 완료되면, 현재 사용자의 정보(누구인지, 권한은 무엇인지)를 이곳에 저장합니다. 개발자는 언제든 여기서 currentUser 정보를 꺼내 쓸 수 있다.
여기서 잠깐! 서블릿 필더란?
클라이언트(사용자)의 요청이 최종 목적지(서블릿/컨트롤러)에 도달하기 전과 후에 작동하는 중간 검문소 라고 생각하면 된다.
가장 쉬운 비유는 수돗물과 정수기이다.
- 수돗물 (요청, Request): 배관을 타고 들어온다.
- 정수기 필터 (Servlet Filter): 물속의 불순물(권한 없는 요청, 잘못된 인코딩)을 걸러낸다.
- 깨끗한 물 (서블릿에 도착): 필터를 통과한 물만 컵(Controller)에 담는다.
만약 필터에서 "이 물은 너무 더러워서 못 마셔!"라고 판단하면 컵으로 물을 보내지 않고 바로 하수구로 버릴 수도 있다(요청 차단).
기술적 동작 위치와 흐름
웹 애플리케이션의 흐름을 보면 위치가 명확해진다.
- 요청(Request) 발생: 사용자가 로그인을 시도
- 필터(Filter) 진입: 요청이 톰캣(WAS)에 도착하면 자바 프로그램(Controller)으로 들어가기 전에 필터를 먼저 만남
- 검사 및 처리: 필터는 요청을 확인
- "한글이 깨지지 않게 인코딩을 UTF-8로 바꿔야겠다."
- "어? 너 로그인 안 했네? 들어가면 안 돼" (여기서 막히면 뒤로 못 감)
- 체인(Chain): 필터는 하나만 있는 게 아니라 여러 개가 사슬(Chain)처럼 연결될 수 있다. 1번 필터를 통과하면 2번 필터로 넘어간다.
- 서블릿(Servlet) 도착: 모든 필터를 무사히 통과하면 그제야 우리가 짠 코드(@Controller)가 실행된다.
코드로 보는 동작 원리 (chain.doFilter)
필터가 어떻게 다음 단계로 넘겨주는지 코드를 보자. 핵심은 chain.doFilter() 메서드이다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 1. [전처리] 요청이 서블릿으로 가기 전에 실행되는 코드
// 예: "사용자가 들어왔다!" 로그 찍기, 한글 인코딩 설정
System.out.println("--- 필터 시작: 문을 통과합니다 ---");
// -------------------------------------------------------
// 중요! 바통 터치 (다음 필터나 서블릿으로 넘겨주는 역할)
chain.doFilter(request, response);
// -------------------------------------------------------
// 2. [후처리] 서블릿이 일을 다 마치고 나갈 때 실행되는 코드
// 예: 응답 데이터 압축하기
System.out.println("--- 필터 종료: 나갈 때 배웅합니다 ---");
}
만약 중간에 chain.doFilter()를 호출하지 않으면? 요청은 거기서 멈추고 컨트롤러까지 도달하지 못한다. (보안 검사에 걸린 상황이 보통 이렇다.)
Spring Security와의 관계
이제 Spring Security와 연결해 보자.
Spring Security는 거대한 필터들의 집합입니다. 스프링 개발팀은 보안 관련 처리는 컨트롤러에 코드를 짜지 말고, 아예 들어오기 전인 필터 단계에서 다 막아버리자! 라고 생각했다.
- 스프링 시큐리티를 켜면, 자동으로 약 10~15개의 보안 전용 필터들이 우리 앱의 입구에 배치된다.
- 어떤 필터는 로그인했나? 만 확인하고, 어떤 필터는 관리자 권한인가? 만 확인한다.
- 이 필터들이 앞에서 철통같이 지키고 있기 때문에, 개발자는 컨트롤러 안에서 복잡한 보안 검사 코드를 짤 필요가 없어지는 것이다.
3. 주요 기능 및 장점
- 표준 로그인/로그아웃 제공: 복잡한 로직 없이 폼 로그인, Basic Auth 등을 쉽게 구현할 수 있다.
- 비밀번호 암호화: BCryptPasswordEncoder 등을 제공하여 비밀번호를 안전하게 해싱하여 저장한다.
- 공격 방어: CSRF(사이트 간 요청 위조), 세션 고정 공격 등 일반적인 웹 보안 취약점을 기본적으로 막아준다.
- 확장성 (OAuth2, JWT): 소셜 로그인(구글, 카카오 등)이나 REST API를 위한 JWT(JSON Web Token) 인증 방식과도 잘 결합된다.
4. 최신 설정 방법 (Spring Boot 3.x 기준)
과거에는 WebSecurityConfigurerAdapter를 상속받아 사용했으나, 현재는 SecurityFilterChain을 Bean으로 등록하는 방식을 사용한다. (Lambda DSL 방식 권장)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF 비활성화 (REST API 서버의 경우 보통 비활성화)
.csrf((csrf) -> csrf.disable())
// HTTP 요청에 대한 권한 설정
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/join").permitAll() // 누구나 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근 가능
.anyRequest().authenticated() // 나머지는 로그인해야 접근 가능
)
// 로그인 폼 설정
.formLogin((form) -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
);
return http.build();
}
// 비밀번호 암호화 Bean 등록
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
결국 Spring Security는 "누가 들어왔는지 확인하고(인증), 갈 수 있는 곳을 통제하며(인가), 나쁜 놈들을 막아주는(보안 방어)" 역할을 하는 스프링의 필수 라이브러리이다.
'tech > Spring' 카테고리의 다른 글
| [Spring] 스프링 빈과 스프링 컨테이너 (0) | 2025.12.16 |
|---|---|
| [Spring] 인증(Authentication)과 인가(Authorization) (0) | 2025.12.03 |
| [Spring] @Valid와 @Validated (1) | 2025.11.12 |
| Java에서 컬렉션 sort 동작 방식 (0) | 2025.11.05 |
| [Spring] @ActiveProfiles("test") vs @DataJpaTest (0) | 2025.08.21 |