CORS 개념에 대해 정리하고, Spring에서 CORS 설정을 해보자 🤔
실습에서 클라이언트는 포트 3000번, 서버는 8081번으로 실행했다. 클라이언트에서 서버로 API를 요청했는데 아래와 같이 브라우저 콘솔창에 에러가 뜨며 응답을 읽을 수 없었다.

읽어보면, CORS 정책 때문에 이 요청이 차단됐고 요청된 리소스에 `Access-Control-Allow-Origin` 헤더가 없다고 한다. 이 문제를 해결하기 위해 CORS의 개념을 공부하고 설정 방법을 정리하고자 한다.
1. CORS (Cross-Origin Resource Sharing)
1-1. CORS 개념

- 동일 출처 정책인 SOP(Same-Origin Policy)를 확장한 정책
- 외부 리소스에 대한 접근을 제어하는 브라우저 메커니즘
- 허용할 출처를 HTTP header에 설정해주면, 다른 출처(cross-origin)의 요청을 허용한다.
=> 즉, HTTP 헤더를 사용하여 다른 출처의 리소스에 접근할 수 있는 권한을 부여하는 브라우저 메커니즘
브라우저는 보안상의 이유로 다른 *출처에서 HTTP 요청을 제한하는 SOP(Same-Origin Policy, 동일 출처 정책)를 따르기 떄문에, 다른 출처(cross-origin)의 요청을 허용하려면 서버의 동의가 필요하다.
나는 CORS를 허용하지 않고 http://localhost:3000
에서 http://localhost:8081
에 자원을 요청해 에러가 발생한 것이다.
^여기서 주의할 건 CORS는 브라우저 메커니즘이며 서버에 구현된 기능이 아니다. 따라서 클라이언트에서 api를 요청했을 때 서버에는 정상적으로 요청이 실행되며 로그가 찍히고 클라이언트 브라우저에서 못읽는 것이다.^ (그러니 서버에서 다른 서버로 api를 요청하면 CORS 에러로부터 자유로워 진다)
모든 리소스에 대해 요청이 차단되는 것은 아니다.
`<img>`, `<video>`, `<script>`, `<link>` 등은 기본적으로 cross-origin 정책을 따르고,
XMLHttpRequest, Fetch API 스크립트 등은 기본적으로 same-origin 정책을 따른다.
^즉, `<img>`, `<video>` 같은 애들은 CORS 설정이 필요없지만 XMLHttpRequest, Fetch API 스크립트 같은 애들은 CORS 설정이 필요하다.^
* 출처(Origin)?
- 출처는 서버의 위치를 찾아가기 위해 필요한 (Protocol, Host, Port번호)를 합쳐놓은 것이다. 이 세개의 값 중 하나라도 다르다면 다른 출처인 것이다.

- 위와 같은 경우에 `https://section.blog.naver.com`라면 동일 출처로 간주한다.
(http의 기본 포트는 80, https의 기본 포트는 443 이다) - `http://localhost:3000`과 같은 출처로 인정되는 것들은 다음과 같다.
접근 URL | 동일 출처 | 이유 |
http://localhost:3000/hello | O | 동일 protocol, host, port |
http://localhost:3000/hello/test | O | 동일 protocol, host, port |
https://localhost:3000 | X | 다른 protocol |
http://www.localhost:3000 | X | 다른 host |
http://localhost:8081 | X | 다른 port |
1-2. CORS 설정이 왜 필요할까?
기본 SOP 정책을 따르면 동일한 출처에게만 요청을 허용해 서비스 이용에 제한이 있을 것이다. SOP가 없다면 모든 곳에서 데이터를 요청할 수 있게 되고, 악의적인 요청이 가능해진다. 만약 유저가 A사이트에서 로그인 한 뒤, A사이트를 흉내내는 B사이트에 접근했다고 하자. 여기서 사용자 권한을 이용해 A사이트 서버에 유저 정보를 요청한다면 다른 출처인 B사이트에서 정상적으로 유저 정보가 조회되고 만다.
따라서 허용된 일부 출처들에게만 리소스를 허용하는 설정이 필요하다.
2. 동작원리


- 브라우저는 요청 header의 `Origin` 필드에 현재 출처를 담아서 보낸다.
- 그럼 서버는 요청에 대한 응답을 반환할 때, `Access-Control-Allow-Origin` 필드를 포함하여 응답한다. 이 필드에는 허용된 출처 정보가 포함된다.
- 만약 요청을 보낸 출처가 서버의 허용된 출처 목록에 있다면 리소스를 받아오고, 허용된 출처 목록에 없다면 브라우저는 응답을 거부하고 CORS 오류가 발생한다.
기본적인 동작 흐름은 위와 같지만, 실제 CORS가 동작하는 방식은 Preflight Request, Simple Request, Credentialed Request 세 가지로 나뉜다.
2-1. Preflight Request (예비 요청)
브라우저는 본 요청을 보내기 전에 서버와 잘 통신되는지 확인하기 위해 사전 요청을 보낸다. 이런 사전 요청을 Preflight 요청이라고 하며, Http method는 OPTIONS를 사용한다. (OPTIONS는 body없이 header만 전송한다)
동작 방식
- 브라우저는 OPTIONS 메서드로 예비 요청을 보낸다.
1) `Origin` 헤더에 현재 출처를 넣고
2) `Access-Control-Request-Headers` 헤더에 본 요청에 사용할 헤더를 설정하고
3) `Access-Control-Request-Methods` 헤더에 본 요청에 사용할 메서드를 설정한다. - 서버는 예비 요청에 대한 응답으로 어떤 것을 허용하는지에 대한 헤더 정보를 담아서 전달한다.
1) `Access-Control-Allow-Origin` 헤더에 허용되는 출처들의 목록을 담고
2) `Access-Control-Allow-Headers` 헤더에 허용되는 헤더를 설정하고
3) `Access-Control-Allow-Methods` 헤더에 허용되는 메서드를 설정하고
4) `Access-Control-Max-Age` 헤더에 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 설정한다. - 브라우저가 서버에서 온 응답을 보고 유효한 요청인지 확인한다.
유효한 요청이라면 원래 요청으로 보내려던 요청을 다시 요청하여 리소스를 응답받는다.
만약 유효하지 않은 요청이라면 요청은 중단되고 CORS 에러가 발생한다.
2-2. Simple Request (단순 요청)
사전 요청을 생략하고 바로 서버에 본 요청을 보내는 방식이다. 다음 조건을 만족해야 한다.
- HTTP method가 GET, HEAD, POST 중 하나이면서
- 자동으로 설정되는 헤더는 제외하고, 설정할 수 있는 헤더들만(`Accept`, `Accept-Language`, `Content-Language`, `Content-Type`, `Range`) 변경하면서
- `Content-Type`이 `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain` 중 하나인 요청이다.
(대부분의 api 요청은 Content-Type이 `text/xml` or `application/json`으로 통신하기 때문에 2-1. Preflight 요청을 보내긴 한다)
동작방식
- 브라우저는 `Origin` 헤더에 해당 출처를 담아 요청을 보낸다.
- 서버는 요청에 대한 응답을 반환할 때, `Access-Control-Allow-Origin` 필드를 포함하여 응답한다.
- 브라우저가 요청한 헤더의 `Origin`값과 서버에서 전달된 헤더의 `Access-Control-Allow-Origin`값을 비교해 유효한 요청이라면 리소스를 응답한다. 만약 유효하지 않은 요청이라면 요청은 중단되고 CORS 에러가 발생한다.
2-3. Credentialed Request (인증된 요청)
자격 인증 정보에 사용되는 쿠키, 토큰 등을 담아 요청할 때 사용되는 방식이다.
기본적으로 브라우저가 제공하는 요청 api들은 인증과 관련된 데이터를 요청 데이터에 담지 않도록 되어 있다. 따라서 Client 코드에서 credentials 옵션을 설정해줘야 인증 정보를 전달할 수 있다.
(요청 방법에 따라 클라이언트에서 credentials 옵션 설정 문법이 다른데, 나는 이번 실습에서 axios 라이브러리를 사용해 `withCredentials: true`를 설정해 줬다)
클라이언트에서 credentials 옵션을 설정했다면, 서버에도 설정을 해줘야 한다.
- `Access-Control-Allow-Credentials` 헤더는 true로 설정해야 하고
- `Access-Control-Allow-Origin`는 와일드카드(*)를 사용할 수 없다.
위 조건을 만족하지 않으면 Credentialed 요청에서는 CORS 에러가 발생한다. (단순 GET요청이 아닌 Credentialed 요청은 Preflight가 먼저 일어난다)
3. Spring 설정 방법
spring에서 CORS 설정해주는 방법은 다양한데 여기서는 `@CrossOrigin` 어노테이션을 사용한 방법과 `spring-security`의 CORS 필터를 사용하는 방법 2가지를 정리했다. 나는 실무에서 주로 CORS 필터를 이용했다.
1) @CrossOrigin
@RestController
@RequestMapping("/")
public class HelloController {
@CrossOrigin(origins="http://localhost:3000")
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/test")
public String test() {
return "test";
}
}
- 컨트롤러 메서드에
@CrossOrigin
을 붙이는 방법 (origins의 default값은 "*"이다) - 위와 같이 설정한다면
http://localhost:3000
에게http://localhost:8081/hello
의 응답이 정상적으로 넘어오고,http://localhost:8081/test
로 요청한다면 CORS 에러가 발생한다.
@CrossOrigin(origins="http://localhost:3000")
@RestController
@RequestMapping("/")
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/test")
public String test() {
return "test";
}
}
- 컨트롤러 클래스에
@CrossOrigin
을 붙이는 방법 http://localhost:3000
에게http://localhost:8081/hello
와http:localhost:8081/test
의 응답이 정상적으로 넘어온다.
2) CorsFilter
`spring-boot-starter-security` 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
spring-security의 CorsFilter 자식클래스로 CustomCorsFilterConfig를 생성해 다음과 같이 설정해준다.
public class CustomCorsFilterConfig extends CorsFilter {
public CustomCorsFilterConfig() {
super(configurationSource());
}
private static CorsConfigurationSource configurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
// 특정 요청 경로에 설정한 config값을 적용
source.registerCorsConfiguration("/**", config);
return source;
}
}
setAllowedOrigins()
: 허용할 출처를 설정 (와일드카드 불가능)setAllowedMethods()
: 허용할 Http method 설정setAllowedHeaders()
: 허용할 Http header 설정setAllowCredentials()
: 인증된 요청을 허용하기 위한 설정- `setMaxAge()` : 특정 시간만큼 preflight 요청을 캐싱하도록 설정
상속받은 CorsFilter를 살펴보면 다음과 같다.
생성될 때 CorsConfigurationSource를 파라미터로 전달받고, 해당 Filter가 실행(`doFilterInternal()`)될 때 설정된 configSource를 이용해 요청과 응답을 처리한다.

* HTTP method - HEAD ?
GET방식과 동일하지만, 응답에 Body가 없고 응답코드와 Header만 존재한다. 웹서버 정보를 확인하기 위해 사용된다.
ex) 헬스체크, 버젼확인, 최종 수정일자 확인 등의 용도로 사용
참고자료 😃
https://evan-moon.github.io/2020/05/21/about-cors/
https://engkimbs.tistory.com/781
https://hannut91.github.io/blogs/infra/cors
https://enjoydev.life/blog/frontend/10-cors
'Web' 카테고리의 다른 글
[Web] 웹 스토리지 (localStorage & sessionStorage) (0) | 2024.04.02 |
---|---|
[Web] Web Server & WAS (0) | 2024.03.30 |
[Web] RESTful API (0) | 2024.03.10 |
[Web] LocalStorage & SessionStorage 개념 및 사용법 (0) | 2024.03.10 |
[Web] REFERER 체크 & CSRF 방어 (0) | 2024.03.09 |
CORS 개념에 대해 정리하고, Spring에서 CORS 설정을 해보자 🤔
실습에서 클라이언트는 포트 3000번, 서버는 8081번으로 실행했다. 클라이언트에서 서버로 API를 요청했는데 아래와 같이 브라우저 콘솔창에 에러가 뜨며 응답을 읽을 수 없었다.

읽어보면, CORS 정책 때문에 이 요청이 차단됐고 요청된 리소스에 Access-Control-Allow-Origin
헤더가 없다고 한다. 이 문제를 해결하기 위해 CORS의 개념을 공부하고 설정 방법을 정리하고자 한다.
1. CORS (Cross-Origin Resource Sharing)
1-1. CORS 개념

- 동일 출처 정책인 SOP(Same-Origin Policy)를 확장한 정책
- 외부 리소스에 대한 접근을 제어하는 브라우저 메커니즘
- 허용할 출처를 HTTP header에 설정해주면, 다른 출처(cross-origin)의 요청을 허용한다.
=> 즉, HTTP 헤더를 사용하여 다른 출처의 리소스에 접근할 수 있는 권한을 부여하는 브라우저 메커니즘
브라우저는 보안상의 이유로 다른 *출처에서 HTTP 요청을 제한하는 SOP(Same-Origin Policy, 동일 출처 정책)를 따르기 떄문에, 다른 출처(cross-origin)의 요청을 허용하려면 서버의 동의가 필요하다.
나는 CORS를 허용하지 않고 http://localhost:3000
에서 http://localhost:8081
에 자원을 요청해 에러가 발생한 것이다.
여기서 주의할 건 CORS는 브라우저 메커니즘이며 서버에 구현된 기능이 아니다. 따라서 클라이언트에서 api를 요청했을 때 서버에는 정상적으로 요청이 실행되며 로그가 찍히고 클라이언트 브라우저에서 못읽는 것이다. (그러니 서버에서 다른 서버로 api를 요청하면 CORS 에러로부터 자유로워 진다)
모든 리소스에 대해 요청이 차단되는 것은 아니다.
<img>
, <video>
, <script>
, <link>
등은 기본적으로 cross-origin 정책을 따르고,
XMLHttpRequest, Fetch API 스크립트 등은 기본적으로 same-origin 정책을 따른다.
즉, <img>
, <video>
같은 애들은 CORS 설정이 필요없지만 XMLHttpRequest, Fetch API 스크립트 같은 애들은 CORS 설정이 필요하다.
* 출처(Origin)?
- 출처는 서버의 위치를 찾아가기 위해 필요한 (Protocol, Host, Port번호)를 합쳐놓은 것이다. 이 세개의 값 중 하나라도 다르다면 다른 출처인 것이다.

- 위와 같은 경우에
https://section.blog.naver.com
라면 동일 출처로 간주한다.
(http의 기본 포트는 80, https의 기본 포트는 443 이다) http://localhost:3000
과 같은 출처로 인정되는 것들은 다음과 같다.
접근 URL | 동일 출처 | 이유 |
http://localhost:3000/hello | O | 동일 protocol, host, port |
http://localhost:3000/hello/test | O | 동일 protocol, host, port |
https://localhost:3000 | X | 다른 protocol |
http://www.localhost:3000 | X | 다른 host |
http://localhost:8081 | X | 다른 port |
1-2. CORS 설정이 왜 필요할까?
기본 SOP 정책을 따르면 동일한 출처에게만 요청을 허용해 서비스 이용에 제한이 있을 것이다. SOP가 없다면 모든 곳에서 데이터를 요청할 수 있게 되고, 악의적인 요청이 가능해진다. 만약 유저가 A사이트에서 로그인 한 뒤, A사이트를 흉내내는 B사이트에 접근했다고 하자. 여기서 사용자 권한을 이용해 A사이트 서버에 유저 정보를 요청한다면 다른 출처인 B사이트에서 정상적으로 유저 정보가 조회되고 만다.
따라서 허용된 일부 출처들에게만 리소스를 허용하는 설정이 필요하다.
2. 동작원리


- 브라우저는 요청 header의
Origin
필드에 현재 출처를 담아서 보낸다. - 그럼 서버는 요청에 대한 응답을 반환할 때,
Access-Control-Allow-Origin
필드를 포함하여 응답한다. 이 필드에는 허용된 출처 정보가 포함된다. - 만약 요청을 보낸 출처가 서버의 허용된 출처 목록에 있다면 리소스를 받아오고, 허용된 출처 목록에 없다면 브라우저는 응답을 거부하고 CORS 오류가 발생한다.
기본적인 동작 흐름은 위와 같지만, 실제 CORS가 동작하는 방식은 Preflight Request, Simple Request, Credentialed Request 세 가지로 나뉜다.
2-1. Preflight Request (예비 요청)
브라우저는 본 요청을 보내기 전에 서버와 잘 통신되는지 확인하기 위해 사전 요청을 보낸다. 이런 사전 요청을 Preflight 요청이라고 하며, Http method는 OPTIONS를 사용한다. (OPTIONS는 body없이 header만 전송한다)
동작 방식
- 브라우저는 OPTIONS 메서드로 예비 요청을 보낸다.
1)Origin
헤더에 현재 출처를 넣고
2)Access-Control-Request-Headers
헤더에 본 요청에 사용할 헤더를 설정하고
3)Access-Control-Request-Methods
헤더에 본 요청에 사용할 메서드를 설정한다. - 서버는 예비 요청에 대한 응답으로 어떤 것을 허용하는지에 대한 헤더 정보를 담아서 전달한다.
1)Access-Control-Allow-Origin
헤더에 허용되는 출처들의 목록을 담고
2)Access-Control-Allow-Headers
헤더에 허용되는 헤더를 설정하고
3)Access-Control-Allow-Methods
헤더에 허용되는 메서드를 설정하고
4)Access-Control-Max-Age
헤더에 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 설정한다. - 브라우저가 서버에서 온 응답을 보고 유효한 요청인지 확인한다.
유효한 요청이라면 원래 요청으로 보내려던 요청을 다시 요청하여 리소스를 응답받는다.
만약 유효하지 않은 요청이라면 요청은 중단되고 CORS 에러가 발생한다.
2-2. Simple Request (단순 요청)
사전 요청을 생략하고 바로 서버에 본 요청을 보내는 방식이다. 다음 조건을 만족해야 한다.
- HTTP method가 GET, HEAD, POST 중 하나이면서
- 자동으로 설정되는 헤더는 제외하고, 설정할 수 있는 헤더들만(
Accept
,Accept-Language
,Content-Language
,Content-Type
,Range
) 변경하면서 Content-Type
이application/x-www-form-urlencoded
,multipart/form-data
,text/plain
중 하나인 요청이다.
(대부분의 api 요청은 Content-Type이 text/xml
or application/json
으로 통신하기 때문에 2-1. Preflight 요청을 보내긴 한다)
동작방식
- 브라우저는
Origin
헤더에 해당 출처를 담아 요청을 보낸다. - 서버는 요청에 대한 응답을 반환할 때,
Access-Control-Allow-Origin
필드를 포함하여 응답한다. - 브라우저가 요청한 헤더의
Origin
값과 서버에서 전달된 헤더의Access-Control-Allow-Origin
값을 비교해 유효한 요청이라면 리소스를 응답한다. 만약 유효하지 않은 요청이라면 요청은 중단되고 CORS 에러가 발생한다.
2-3. Credentialed Request (인증된 요청)
자격 인증 정보에 사용되는 쿠키, 토큰 등을 담아 요청할 때 사용되는 방식이다.
기본적으로 브라우저가 제공하는 요청 api들은 인증과 관련된 데이터를 요청 데이터에 담지 않도록 되어 있다. 따라서 Client 코드에서 credentials 옵션을 설정해줘야 인증 정보를 전달할 수 있다.
(요청 방법에 따라 클라이언트에서 credentials 옵션 설정 문법이 다른데, 나는 이번 실습에서 axios 라이브러리를 사용해 withCredentials: true
를 설정해 줬다)
클라이언트에서 credentials 옵션을 설정했다면, 서버에도 설정을 해줘야 한다.
Access-Control-Allow-Credentials
헤더는 true로 설정해야 하고Access-Control-Allow-Origin
는 와일드카드(*)를 사용할 수 없다.
위 조건을 만족하지 않으면 Credentialed 요청에서는 CORS 에러가 발생한다. (단순 GET요청이 아닌 Credentialed 요청은 Preflight가 먼저 일어난다)
3. Spring 설정 방법
spring에서 CORS 설정해주는 방법은 다양한데 여기서는 @CrossOrigin
어노테이션을 사용한 방법과 spring-security
의 CORS 필터를 사용하는 방법 2가지를 정리했다. 나는 실무에서 주로 CORS 필터를 이용했다.
1) @CrossOrigin
@RestController
@RequestMapping("/")
public class HelloController {
@CrossOrigin(origins="http://localhost:3000")
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/test")
public String test() {
return "test";
}
}
- 컨트롤러 메서드에
@CrossOrigin
을 붙이는 방법 (origins의 default값은 "*"이다) - 위와 같이 설정한다면
http://localhost:3000
에게http://localhost:8081/hello
의 응답이 정상적으로 넘어오고,http://localhost:8081/test
로 요청한다면 CORS 에러가 발생한다.
@CrossOrigin(origins="http://localhost:3000")
@RestController
@RequestMapping("/")
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/test")
public String test() {
return "test";
}
}
- 컨트롤러 클래스에
@CrossOrigin
을 붙이는 방법 http://localhost:3000
에게http://localhost:8081/hello
와http:localhost:8081/test
의 응답이 정상적으로 넘어온다.
2) CorsFilter
spring-boot-starter-security
의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
spring-security의 CorsFilter 자식클래스로 CustomCorsFilterConfig를 생성해 다음과 같이 설정해준다.
public class CustomCorsFilterConfig extends CorsFilter {
public CustomCorsFilterConfig() {
super(configurationSource());
}
private static CorsConfigurationSource configurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
// 특정 요청 경로에 설정한 config값을 적용
source.registerCorsConfiguration("/**", config);
return source;
}
}
setAllowedOrigins()
: 허용할 출처를 설정 (와일드카드 불가능)setAllowedMethods()
: 허용할 Http method 설정setAllowedHeaders()
: 허용할 Http header 설정setAllowCredentials()
: 인증된 요청을 허용하기 위한 설정setMaxAge()
: 특정 시간만큼 preflight 요청을 캐싱하도록 설정
상속받은 CorsFilter를 살펴보면 다음과 같다.
생성될 때 CorsConfigurationSource를 파라미터로 전달받고, 해당 Filter가 실행(doFilterInternal()
)될 때 설정된 configSource를 이용해 요청과 응답을 처리한다.

* HTTP method - HEAD ?
GET방식과 동일하지만, 응답에 Body가 없고 응답코드와 Header만 존재한다. 웹서버 정보를 확인하기 위해 사용된다.
ex) 헬스체크, 버젼확인, 최종 수정일자 확인 등의 용도로 사용
참고자료 😃
https://evan-moon.github.io/2020/05/21/about-cors/
https://engkimbs.tistory.com/781
https://hannut91.github.io/blogs/infra/cors
https://enjoydev.life/blog/frontend/10-cors
'Web' 카테고리의 다른 글
[Web] 웹 스토리지 (localStorage & sessionStorage) (0) | 2024.04.02 |
---|---|
[Web] Web Server & WAS (0) | 2024.03.30 |
[Web] RESTful API (0) | 2024.03.10 |
[Web] LocalStorage & SessionStorage 개념 및 사용법 (0) | 2024.03.10 |
[Web] REFERER 체크 & CSRF 방어 (0) | 2024.03.09 |