CORS와 스프링에서의 해결법
0. 들어가기 전에
장바구니 미션을 하면서 문제가 되었던 CORS를 알아보고 해결한 내용을 정리해 보았습니다.
1. CORS란?
교차 출처 리소스 공유(Cross-Oriign Resource Sharing, CORS)
브라우저는 동일 출처 정책(Same-Origin Policy, SOP)을 사용함으로써 XSS나 XSRF와 같은 공격을 방어합니다. 하지만 실제 웹페이지에서는 자주 다른 출처의 자원을 사용하게 되기 때문에 CORS를 사용합니다. CORS는 추가 HTTP 헤더를 사용해서, 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 여기서 말하는 출처(Origin)란 URL에서 Protocol, Host, Port를 의미합니다.
아래 URL을 살펴보면
https://www.test.com:443/hello/world?page=1#number
https
는 Protocol, www.test.com
이 Host, :443
이 포트를 의미합니다.
따라서 이 3개 중 하나가 다르면 브라우저는 다른 출처라고 인식하게 됩니다.
2. 문제 상황
2.1 단순 요청(Simple Request)
아래의 조건을 모두 만족하는 요청을 단순 요청이라고 합니다.
[조건]
- 다음 중 하나의 HTTP Method
GET
HEAD
POST
- 사용할 수 있는 Header
Accept
Accept-Language
Content-Language
Content-Type
- 사용할 수 있는 Content-Type
application/x-www-form-urlencoded
multipart/form-data
text/plain
브라우저가 서버에 API 요청을 하고 서버는 Access-Control-Allow-Origin
헤더를 포함한 응답을 보내, 브라우저가 CORS를 수행할지 결정합니다. 만약 Access-Control-Allow-Origin
이 *
이면 모든 Origin을 허용한 것이므로 CORS를 허용해 줍니다. 범위를 줄이려면 브라우저가 요청한 Origin
헤더와 같은 값을 서버가 Access-Control-Allow-Origin
으로 넣어주면 됩니다. 다만 이때는 Vary
헤더를 추가로 사용해 Origin
을 넣어줘야 합니다.
하지만 브라우저와 서버는 api를 통해 통신을 할 때, 대부분 application/json의 값을 전달해 주기 때문에 일반적으로는 사용하지 않습니다.
2.2 프리플라이트 요청(Preflighted Request)
Http Method의 PATCH
, DELETE
나 Content-Type이 application/json
을 사용하는 요청은 프리플라이트 요청으로 동작하게 됩니다.
Simple Request와 달리 Http Method의 Options
메서드를 통해 실제 요청을 전송하기에 안전한지 미리 확인하는 절차를 거칩니다. 일련의 예비 전송 절차라고 생각할 수 있습니다.
만약 프리플라이트 요청이 없다면, 어떤 일이 발생할 수 있을까요? CORS의 기본 동작 과정을 살펴봅시다. 기본적으로 서버는 CORS 정책을 모릅니다. 그래서 서버는 브라우저가 요청하면 일단 처리하고 값을 반환합니다. 그러고 나서 브라우저가 정책에 맞는지 안 맞는지 확인 후, 응답 값을 받을지 버릴지 결정하게 됩니다.
이런 동작을 이해하면 프리플라이트가 필요한 이유도 자연스럽게 알 수 있습니다. 브라우저가 악의적 요청을 보내면 서버는 무조건 처리하게 됩니다. 그 요청의 Origin이 서버에서 허락이 안 되어있어도 말이죠. 그래서 프리플라이트를 사용해서 브라우저가 안전한지 미리 확인합니다.
Preflight 요청은 위에서 설명한 것과 같고, Main Request도 Simple Request와 같은 방식으로 동작합니다.
프리플라이트는 Options
메서드를 사용하기 때문에 서버 측에서 Options
메서드를 풀어줘야 합니다. 또한, Access-Control-Max-Age
헤더를 통해 프리플라이트를 캐싱도 할 수 있습니다.
2.3 인증정보를 포함한 요청(Credentialed Request)
브라우저가 cookie
를 포함한 요청을 하는 경우입니다. 만약 서버에서 Access-Control-Allow-Credentials
이 true가 아니면 CORS 에러가 나옵니다.
추가로 인증정보를 포함한 요청에서는 Access-Control-Allow-Origin
의 값이 *
가 될 수 없고, 특정한 URL을 입력해줘야 합니다. 그렇기 때문에 서버에 응답에는 Vary
헤더도 필요하게 됩니다.
3. 스프링에서 해결법
스프링에서는 @CrossOrign
를 사용하거나 필터를 이용한 방법도 있지만, 전역으로 설정할 수 있는 방법인 WebMvcConfigurer
를 통한 방법을 알아보겠습니다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.exposedHeaders(LOCATION)
.allowedOrigins("프론트 주소")
.maxAge(3600);
}
}
WebMvcConfigurer
를 구현한 Config파일을 만들어 주고 addCorsMappings()
메서드를 Override 해줍니다. 그 후 위의 코드와 같이 설정을 해주면 됩니다.
Cors 설정을 도와주는 CorsRegistry에 CorsRegistration을 빌더 패턴을 이용해서 등록합니다. 사용된 메서드의 설명은 아래와 같습니다.
addMapping()
: 요청을 수행할 서버 API URI 추가합니다. 여기에선/**
을 입력했으므로 서버 API 모두 접근할 수 있게 됩니다.allowedMethods()
: 요청을 허락할 Http Method를 추가합니다. 여기에서는 와일드카드를 입력했으므로 전체 Http Method가 요청으로 들어올 수 있게 됩니다.exposedHeaders()
: 프론트에서 접근할 수 있는 Header를 추가합니다. CORS 정책에서는 Location 헤더를 기본적으로 노출하지 않고 있어서, 201 Created에서 반환하는 Location에 접근을 못 하게 되어서 풀게 되었습니다.allowedOrigins()
: 서버에 접근을 허용할 프론트 주소를 추가합니다.maxAge()
: preflight를 캐싱할 시간을 정합니다. 프리플라이트를 계속 요청하게 되면 성능에 문제가 생기므로 적당한 시간을 주어 캐싱하게 했습니다.
※ 참조
'개발 > [오류]' 카테고리의 다른 글
Interceptor에서 JWT를 사용할 때 주의점 (1) | 2024.02.07 |
---|---|
Redirect URL에 쿼리 파라미터가 생기는 경우 (0) | 2022.04.14 |
Embedded Type Test 문제와 H2 GenerationType 문제 (0) | 2022.04.12 |
ModelAttribute 관련 오류 (0) | 2022.03.23 |
테스트 데이터 격리 (0) | 2022.03.18 |