
Spring Security를 적용하다가 재미있는 상황을 겪었다.
Spring Boot만 쓰다가 xml 설정을 처음해보면서 겪은 시행착오가 Spring의 Context 구조를 이해하게 된 계기가 되어서 기록으로 남겨둔다.
처음에는 당연히 시큐리티 관련 설정이니까 context-security.xml에 넣으면 되겠지 하고 아래 설정을 추가했다.
<!-- context-security.xml -->
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
분명 설정은 제대로 했는데 @AuthenticationPrincipal 어노테이션이 동작하지 않았다. 그래서 이 설정을 dispatcher-servlet.xml로 옮겨봤다.
<!-- dispatcher-servlet.xml -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
그랬더니 신기하게도 잘 동작했다.
이 문제를 제대로 이해하려면 웹 요청이 WAS에서 Spring까지 어떻게 흘러가는지 알아야 한다고 생각해서 추가로 알아보았다.
WAS가 구동되면서 web.xml을 읽는다. 이때 두 가지 중요한 작업이 일어난다:
<!-- web.xml -->
<!-- 1. ContextLoaderListener 등록 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 2. Root Context 위치 지정 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/context-security.xml
</param-value>
</context-param>
여기서 ContextLoaderListener는 Spring의 Root Context를 생성하는 역할을 한다. 마치 건물을 지을 때 기초 공사를 하는 것과 같다.
Root Context가 생성되면서 다음과 같은 일들이 일어난다.
1. context-security.xml, root-context.xml 등을 로드
2. 데이터베이스 연결 설정 초기화
3. 트랜잭션 매니저 생성
4. 보안 필터 체인 구성
이것은 마치 건물의 전기, 수도, 보안 시스템을 설치하는 것과 같다.
그 다음 DispatcherServlet이 초기화된다.
<!-- web.xml -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
DispatcherServlet은 자신만의 Context(Servlet Context)를 생성한다.
이는 마치 건물 안에 각 부서의 사무실을 만드는 것과 같다.
사용자가 웹 페이지를 요청하면 다음과 같은 흐름으로 처리된다.
브라우저 요청
↓
WAS (Tomcat)
↓
web.xml 설정 확인
↓
DispatcherServlet (프론트 컨트롤러)
↓
핸들러 매핑 (어떤 컨트롤러가 처리할지 결정)
↓
컨트롤러 실행 (@AuthenticationPrincipal 처리)
↓
서비스 계층 (Root Context의 빈들 사용)
이제 왜 @AuthenticationPrincipal 설정이 dispatcher-servlet.xml에 있어야 하는지 이해가 된다. 컨트롤러의 파라미터를 처리하는 ArgumentResolver는 요청을 처리하는 시점에 필요하기 때문이다!
지금까지 본 복잡한 설정들을 Spring Boot는 어떻게 처리하는지 살펴보자.
Spring Legacy에서는
<!-- web.xml -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
Spring Boot에서는
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
이게 끝이다. @SpringBootApplication 하나로 설정이 자동화된다. 그립다..
Spring Legacy에서 해야 했던 일들은
1. Root Context 설정 (context-*.xml)
2. Servlet Context 설정 (dispatcher-servlet.xml)
3. web.xml에 각종 설정 추가
Spring Boot에서는
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 보안 설정
}
@Controller
public class UserController {
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal UserDetails userDetails) {
// 메소드 내용
}
}
@AuthenticationPrincipal도 자동으로 처리된다Spring Boot가 시작되면
1. @SpringBootApplication이 컴포넌트 스캔을 시작
2. spring-boot-autoconfigure 라이브러리가 각종 자동 설정 클래스를 로드
3. classpath에 있는 라이브러리를 보고 필요한 설정을 자동으로 구성
예를 들어
// Spring Boot의 자동 설정 예시
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ AuthenticationManager.class })
@EnableWebSecurity
public class SecurityAutoConfiguration {
// 보안 관련 자동 설정
}
물론 자동 설정을 커스터마이징할 수도 있다
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 필요한 ArgumentResolver 추가
}
}
단순히 "여기 넣으니까 됐다/안됐다"로 끝날 수 있는 문제였지만, 이를 통해 Spring의 Context 구조와 설계 철학을 더 깊이 이해하게 됐다. Spring Boot가 이런 복잡한 설정들을 자동화해주는 것도 이제 보니 더 깊이 이해가 된다.
또한 SSAFY에서는 부트만 사용하다보니 내부적으로 이런 흐름을 위한 세세한 설정을 처음 해봐서 더 많은 공부가 되는 동시에 스프링 부트가 그리워졌다......
하지만 부트만 사용한다 해도, 이러한 내부적 흐름이나 xml에 대한 지식들이 문제해결과 최적화에 꼭 필요하겠다는 생각이 들었다.
Spring Boot는 설정보다 관습(Convention over Configuration)이라는 철학을 따르면서, 우리가 앞서 본 복잡한 설정들을 최대한 자동화했다. 하지만 그 내부에서는 여전히 같은 Spring Context 구조가 동작하고 있다는 점을 항상 생각하고 공부해야겠다고 다짐하는 계기가 되었다.