사실 이전의 이론편만 적고 이 CORS의 고통에서는 영원히 해방된줄 알았으나, 그때 쯤 프론트에서 연락이 왔다.
cors 설정 좀 고쳐주세요
https://bogmong.tistory.com/5
간단히 요약하자면, React에서 Axios라이브러리를 통해 POST요청을 보내는데, JWT 토큰이 담겨있는 Authorization 헤더가 안받아진다는 것이다. PostMan에서는 잘 받아지는데 말이다.
딱 이 말을 듣자마자 용의자가 누군지 알 수 있었다. 브라우저다. 분명히 응답을 잘 받았는데, CORS 설정 때문에 필터링했구나 이 녀석 😤
그래서 이번편을 작성하게 되었다. 분명히 CORS는 나의 개발의 삶과 함께 꾸준히 고통받을 녀석인것 같기에, 추후 참고할 만큼 직관적이고, 핵심 코드만 있는 글을 쓰기로 하였다.
Spring Security의 설정에는 두가지 방식이 있다.
첫번째 설정은 Spring Security 5.7.0-M2 부터 deprecated되어 버렸다.
따라서 두번째 방법으로 작성한다.
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity security, AccessToken.TokenBuilder tokenBuilder) throws Exception {
// disables
return security
.cors()
.and()
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
//설정들
return source;
}
}
자 복붙하지말고 공부해보자.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setExposedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
사실 SpringSecurity가 너무 좋은게 이 간단한 설정만으로 Cors 기능을 제공해준다.
대신 이것 조차 공부하기 싫어서 구글에서 복붙해오면, 말그대로 주먹구구식 코딩이 되는 것이다.(나를 말하는 것이다.)
자 CorsConfigurationSource가 무엇인지 보자. 딱 하나의 메서드만 있는 함수형 인터페이스다.
request 에 따라 적절한 CorsConfiguration을 리턴해준다.
기본적으로 많이 쓰는 UrlCorsConfiguration을 보자.
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
//...
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
//...
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
Object path = resolvePath(request);
boolean isPathContainer = (path instanceof PathContainer);
for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (match(path, isPathContainer, entry.getKey())) {
return entry.getValue();
}
}
return null;
}
핵심 부분만 가져왔다. 간단하게 말해서, Map에 PathPattern과 CorsConfiguration을 등록해 놓고, 요청에 적절한 CorsConfiguration을 넘겨주는 것이다.
따라서 구지 관습적으로 UrlCorsConfiguration 를 복붙 할게 아니라, 본인만의 CorsConfiguration을 구현해도 충분하다.
여기가 사실상 핵심 부분이다. 프레임워크가 좋은점이 무엇인가? 설정만 해주면 알아서 돌아간다는 것이다.
설정을 가장 실용적이게 알아보자.
필드와 상수들이다. 보면 이 설정들을 가져다 쓴다고 이해할 수 있다.
@Nullable
private List<String> allowedOrigins;
@Nullable
private List<OriginPattern> allowedOriginPatterns;
둘은 같아보이는데 무슨 차이가 있을까?
"*"
을 줄 수 없다. 이유는 이전편 참고List.of("*")
으로 우회해 사용할 수 있다. ->하지만 절대 권장하지 않는다. 절대그래서 어떻게 설정을 할까?
복붙 블로그 들에는 전부 허용하는 설정만 복사해서 사용하고 있다. 실제상황에서는 어떻게 설정해야할까?
브라우저는 CORS를 체크할 때 메서드도 체크한다. 무슨 말일까?
브라우저 입장 : 우리 도메인에서 너네 다른 도메인에 이러한 메서드로 리소스 보낼건데, 가능?
우리 서버 : ㅇㅇ 쌉가능
그러니까 다른 도메인에서 우리 서버로 GET, POST 처럼 메서드를 보내는게 허용되냐고 묻는것이다. (너무 깐깐하다)
@Nullable
private List<String> allowedMethods;
@Nullable
private List<HttpMethod> resolvedMethods = DEFAULT_METHODS;
//참고
private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
메서드를 설정하는 것도 두가지 옵션이 있다. 구지 큰 차이는 없지만 둘다 허용할 메서드를 지정하라고 되어 있다.
자 복붙 블로그와의 차이를 위해 실전에서 어떻게 설정해야할까?
여기가 좀 까다롭다. 이번 글을 쓰게된 이유도 이 의미를 명확히 파악하지 않아서 쓰게된 것이다.
@Nullable
private List<String> allowedHeaders;
@Nullable
private List<String> exposedHeaders;
둘의 차이를 알겠는가?
그러니까 완전 다르다. allowedHeaders 는 클라이언트가 본 요청(이거 모르면 이전 글 참고)에서 사용할 수 있는 헤더 목록을 나타낸다.
exposedHeaders 는 클라이언트가 응답(리소스)을 받은 후 읽기(접근)이 허용되는 헤더를 말한다.
@Nullable
private Boolean allowCredentials;
@Nullable
private Long maxAge;
credentials에 대해서는 나도 다소 헷갈리는 부분이다. 하지만 내가 이해한 대로 설명하자면, 쿠키와 같은 보안요청을 본 요청에 포함해도 되는지 말해주는 헤더로 이해하고 있다.
대신 이렇게 설정하면 보안이 좀더 빡빡해지는데, 대표적으로 allow origin을 와일드카드(*
)로 설정하지 못한다. (자세한 내용은 이전글 참조)
애초에 이 값을 true로 주고, addAllowedOrigin("*")
과 같이 설정했다면, 내부 로직 중 예외가 발생한다.
(나라면 application 실행시 체크해서 예외를 던져서 실행 자체가 안되게 막을텐데, 런타임 중에 체크한다. 나중에 spring security 에 pr을 보내봐야겠다.)
maxAge의 경우, preflight의 응답을 클라이언트가 캐싱할 수 있는 시간을 지정한다. 따라서 클라이언트가 이 preflight 요청을 자주 갱신해서 cors 세팅을 업데이트하기를 바란다면, 이 값을 짧게 설정하면 될 것이다.