일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 헤리턴스아라
- 몰디브
- 자바스크립트
- 소프트웨어장인정신
- HTTP 완벽가이드
- JavaScript
- html
- Hibernate Reactive
- http
- 자바
- 주간회고
- 신혼여행
- 메가테라
- 상속
- 포트폴리오
- 취업회고
- java
- jvm
- 2022회고
- 부트캠프
- css
- 이펙티브자바
- SpringSecurity
- til
- leetcode
- Spring
- CORS
- 클로저
- http 완벽 가이드
- 바닐라코딩
- Today
- Total
codingBird
Spring Application - CORS 설정 - 2 본문
CORS 와 Preflight 그리고 설정파일 설명이 있었던 1편에 이어서 실제 코드를 보여주는 2편입니다.
1편을 작성하고 바로 2편을 작성하려 했지만 기존 프로젝트에서 @PreAuthorize 을 이용해 Method Security를 적용하는 모습을 보고 테스트 프로젝트에도 Method Security를 구현하고 싶어졌습니다.
그러기 위해서는 SpringContextHolder 그리고 Authentication의 학습이 필요했고 충분히 이해하고 나서야 구현할 수 있었기 때문에 조금 늦게 되었습니다. (사실 젤다의 영향도 아주 조금 있습니다…)
테스트 프로젝트
CORS 설정에 집중할 수 있도록 클라이언트와 서버 모두 최소한의 구현만 갖춘 프로젝트입니다.
다만 개인적인 욕심으로 Spring AOP 을 활용하는 메서드 기반 Security를 적용한 모습을 볼 수 있습니다.
클라이언트: Javascript, React, Axios
서버: Kotlin, SpringMVC, Spring Security
특정 타입 곰을 요청하고 권한이 없다면 에러메시지를 보여줍니다. Request Header에 Authorization: Bearer AdminBearToken 을 넣어주는 것을 제외하면 특별한 부분이 없는 매우 간단한 어플리케이션입니다.
서버
CORS 설정에 집중하기 위해서 비교적 친숙한 WebMVC 프레임워크를 사용했고 UserDetailService와 Token 등 생략할 수 있는 부분은 구현하지 않아 복잡도를 낮추었습니다.
HTTP Request 와 Security
Spring Security를 공부할 때 HTTP Request가 인증을 어떻게 통과하는지 이해할 수 없어 고통스러웠지만 위에 있는 그림을 통해 멘탈 모델을 형성해 완벽하진 않지만 이해하는데 많은 도움을 받았습니다.
실제 구현은 더욱 복잡하겠지만 Spring Security는 등록된 SecurityFilter에게 HTTP Ruquest를 전달하고 SecurityFilter 중 하나라도 SecurityContextHolder에 인증 객체(Authentication Object)를 설정하게 되면 해당 요청은 인증을 통과했다고 보면 될 것 같습니다.
Filter와 Interceptor
이제 WebMvcConfigurer.addCorsMappings와 CorsFilter가 어떻게 작동하는지 설명할 건데 Filter와 Interceptor를 알고 있어야 쉽게 이해할 수 있어 간단하게 설명하고 시작하겠습니다.
Filter
- Filter는 DispatcherServlet 호출 전에 작동합니다.
- J2EE 표준 스펙 기준으로 DispatcherServlet에 요청이 전달되기 전후로 요청에 대해 부가작업을 처리할 수 있는 기능을 제공합니다.
Interceptor
- Interceptor는 DispatcherServlet 호출 후에 작동합니다.
- Interceptor는 Spring이 제공하는 기술로 DispatcherServlet에서 호출되고 Handler 호출 전후의 요청과 응답을 참조하거나 가공하는 기능을 제공합니다. Handler는 대부분 Controller에 해당하는 메서드입니다.
CORS 및 Security Filter 설정
WebMvcConfigurer.addCorsMappings로 글로벌 CORS 설정하거나 CosFilter를 주입하는 방식으로 CORS 요청을 처리하려면 SecurityFilter의 CorsFilter를 disable 해줘야 합니다. Spring Security는 5.0 버전 이후로 자체적인 CORS 처리 매커니즘을 도입했고 FilteChain에 자동으로 CorsFilter가 등록되기 때문입니다.
만약 SecurityFilter에 cors { disable() } 을 하지 않는다면 Spring Security는 자동으로 SecurityFilter에 CorsFilter를 추가합니다. 그리고 SecurityFilter에 있는 Filter는 외부에서 주입한 CorsFilter의 우선순위 보다 높게 설정되기 때문에 CORS 에러가 발생하게 될 것입니다.
(WebMvcConfigurer.addCorsMappings은 CORS 요청을 Handler 혹은 Interceptor 에서 처리하게 되기 때문에 당연히 CORS 에러가 발생하게 됩니다.)
WebMvcConfigurer.addCorsMappings
WebMvcConfigurer.addCorsMappings를 통해 ****글로벌 CORS 설정을 세팅하는 부분은 1편에서 설명했기 때문에 바로 DispatcherServlet에서 CORS 요청을 다루는 부분을 보겠습니다.
DispatcherServlet.doDispatch 메서드는 요청을 처리하기 위해 모든 HandlerMapping의 getHandler 메서드를 호출합니다. 그 중에서도 CORS 관련 처리는 위에 있는 코드를 통해 수행됩니다.
주요 내용은 아래와 같습니다.
- **CorsUtils.isPreFlightRequest(request)**를 통해 현재 요청이 Preflight 요청인지 확인합니다.
- Preflight 요청인 경우, 해당 요청에 대한 핸들러가 없으므로 PreFlightHandler를 생성하고 현재 chain의 interceptors를 사용해 HandlerExecutionChain을 다시 생성합니다.
- Preflight 요청이 아닌 경우 현재 chain에 CorsInterceptor를 추가해 CORS 검증을 수행합니다.
💡 WebMvcConfigurer.addCorsMappings 을 통해 CORS 설정을 한 경우 PreFlightHandler 또는 CorsInterceptor 에서 CORS 검증을 수행된다고 볼 수 있습니다.
CorsFilter
CorsFilter는 Filter입니다. 위에 있는 Filter와 Interceptor 내용을 보셨다면 이미WebMvcConfigurer.addCorsMappings 와 CorsFilter 가 작동하는 ****위치가 다르다는 것을 알 수 있을겁니다! 사소한 부분은 넘어가고 CorsConfigure가 어떻게 CorsFilter를 찾아서 Servlet Container에 등록하는지 바로 알아보도록 하겠습니다. 혹시 프로세스를 자세히 알고싶다면 설정파일에 있는 HttpSecurity.cors 부터 분석해 아래 CorsConfigurer.getCorsFilter 까지 내려오면 되겠습니다.
//CorsConfigurer.class
private CorsFilter getCorsFilter(ApplicationContext context) {
if (this.configurationSource != null) {
return new CorsFilter(this.configurationSource);
}
boolean containsCorsFilter = context
.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
if (containsCorsFilter) {
return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
}
boolean containsCorsSource = context
.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
if (containsCorsSource) {
CorsConfigurationSource configurationSource = context.getBean(
CORS_CONFIGURATION_SOURCE_BEAN_NAME, CorsConfigurationSource.class);
return new CorsFilter(configurationSource);
}
boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
context.getClassLoader());
if (mvcPresent) {
return MvcCorsFilter.getMvcCorsFilter(context);
}
return null;
}
CorsConfigurer가 CorsFilter를 찾는 로직은 아래와 같습니다.
- CorsConfigurer가 CorsConfigurationSource를 설정했는지 확인합니다. 설정되었다면 이를 ****사용해 CorsFilter를 생성합니다.
- 현재 컨텍스트에서 CorsFilter 이름의 인스턴스가 있는지 확인합니다. 있다면 그대로 CorsFilter로 사용합니다
- 현재 컨텍스트에서 corsConfigurationSource 이름의 CorsConfigurationSource 인스턴스가 있는지 확인합니다. 있다면 이를 사용해 CorsFilter를 생성합니다.
- 현재 컨텍스트의 ClassLoader에서 HandlerMappingIntrospector 클래스가 존재하는지 확인합니다. 있다면 이를 통해 MvcCorsFilter 내부 클래스를 사용해 CorsFilter를 생성합니다.
- 아무것도 찾지 못한 경우 null을 반환하고, 사용하는 쪽에서 IllegalStateException 예외를 던져 Spring 초기화를 실패하게 만듭니다.
💡 2번 내용을 통해 우리가 등록한 CorsFilter가 어떤 과정을 거쳐 Servlet container에 등록되는지 알 수 있습니다.
설정 테스트
- CorsFilter + WebMvcConfigurer.addCorsMappings (현재 설정)
당연히 CORS와 PreAuthorize 모두 정상적으로 작동합니다.
2. WebMvcConfigurer.addCorsMappings 단독 설정
CORS와 PreAuthorize 모두 정상적으로 작동합니다.
3. CorsFilter 단독 설정
역시 CORS와 PreAuthorize 모두 정상적으로 작동합니다.
4줄 요약
- WebMvcConfigurer.addCorsMappings 메서드를 구현해 CORS 설정하면 CORS 검증은 Interceptor 또는 Handler에서 작동합니다.
- CorsFilter를 주입하는 방식은 CORS 검증을 Filter에서 실행하게 합니다.
- Spring Security 사용하면 우리가 주입한 CorsFilter가 실행되도록 보장하기 위해 **HttpSecurity.cors{ disable() }**메서드를 호출해야 합니다.
- CorsFilter + WebMvcConfigurer.addCorsMappings 방식은 CORS 검증을 Filter와 Interceptor에서 각각 한 번씩 수행해 비교적 비효율적인 방식입니다.
참고: 테스트 프로젝트 코드는 아래 링크를 통해 확인할 수 있습니다.
'JAVA' 카테고리의 다른 글
Spring Application - CORS 설정 - 1 (0) | 2023.07.11 |
---|---|
[이펙티브 자바] - Item 2 생성자에 매개변수가 많다면 빌더를 고려하라. (0) | 2023.04.15 |
[이펙티브 자바] - Item 1 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2023.04.14 |
모든 프로그래머가 알아야 하는 JVM - Runtime Data Area (0) | 2022.09.10 |
Favor composition over inheritance. (0) | 2022.09.06 |