CORS란? + Spring boot에서의 CORS, Preflight에 관한 이슈

황세호·2021년 7월 27일
0

Spring

목록 보기
1/3

Cross-Origin Resource Sharing(CORS)은 추가적인 HTTP header를 사용해서 애플리케이션이 다른 origin의 리소스에 접근할 수 있도록 하는 메커니즘을 말한다.
허나 실제로는, 다른 origin에서 내 리소스에 함부로 접근하지 못하게 하기 위해 사용된다.

cross-origin 요청의 종류

  • 다른 도메인
  • 다른 하위 도메인
  • 다른 포트
  • 다른 프로토콜

요청 헤더 목록

  • Origin
  • Access-Control-Request-Method
    * preflight 요청을 할 때 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알리기 위해 사용된다.
  • Access-Control-Request-Headers
    * preflight 요청을 할 때 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알리기 위해 사용된다.

응답 헤더 목록

  • Access-Control-Allow-Origin
    브라우저가 해당 origin이 자원에 접근할 수 있도록 허용한다. 혹은 ``은 credentials이 없는 요청에 한해서 모든 origin에서 접근이 가능하도록 허용한다.
  • Access-Control-Expose-Headers
    * 브라우저가 액세스할 수 있는 서버 화이트리스트 헤더를 허용한다.
  • Access-Control-Max-Age
    * 얼마나 오랫동안 preflight 요청이 캐싱될 수 있는지를 나타낸다.
  • Access-Control_Allow-Credentials
    Credentials가 true일 때 요청에 대한 응답이 노출될 수 있는지를 나타낸다.
    preflight 요청에 대한 응답의 일부로 사용되는 경우 실제 자격 증명을 사용하여 실제 요청을 수행할 수 있는지를 나타낸다.
    * 간단한 GET 요청은 preflight되지 않으므로 자격 증명이 있는 리소스를 요청하면 헤더가 리소스와 함께 반환되지 않으면 브라우저에서 응답을 무시하고 웹 콘텐츠로 반환하지 않는다.
  • Access-Control-Allow-Methods
    * preflight 요청에 대한 응답으로 허용되는 메서드들을 나타낸다.
  • Access-Control-Allow-Headers
    * preflight 요청에 대한 응답으로 허용되는 header들을 나타낸다.

CORS는 왜 필요한가?

만약 내가 서비스하고 있지 않은 사이트에서 세션을 요청해서 세션을 획득할 수 있다면 해당 사이트는 악의적으로 내 세션을 탈취하거나 다른 행동을 할 수 있다. 그래서 브라우저에서는 이러한 요청을 막는다.
피싱 사이트가 대표적인 공격 사례인데, 이러한 것을 막고 내가 허용한 origin들만 요청할 수 있도록 하기 위함이다.

CORS의 동작 방식

브라우저가 리소스를 요청할 때, 추가적인 헤더에 정보를 담는다.(내 origin, 사용하는 메서드, 사용하는 헤더) -> 이 헤더를 서버에 보낸다. -> 서버는 서버가 응답할 수 있는 origin들을 헤더에 담아서 브라우저에게 보낸다. -> 브라우저는 이 헤더를 보고 해당 origin에서 요청할 수 있다면 리소스 전송을 한다.(불가능 하다면 에러 발생)

CORS 에러

해결방안

CORS 에러 발생 시 가장 쉬운 해결방안은 서버 단에서 특정 origin 혹은 모든 origin을 허용하도록 설정해주기만 하면 된다.

다음은 Spring boot 기준 CORS 허용 방법 예시이다.

@CrossOrigin 활용

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;

/*
	http://localhost:8080에서 들어오는 모든 요청 CORS 허용
*/
@CrossOrigin(origins = "http://localhost:8080")
@PostMapping("/")
public String postSuccess(){
}

WebConfig 설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    /*
        http://localhost:8080 에서 들어오는 모든 요청 CORS 허용
    */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080");
    }
}

🧐Preflight란?

preflight는 우리말로 하면 말 그대로 미리 보내는 것, 사전 전달이라고 할 수
있다.

기본적으로 브라우저는 cross-origin 요청을 전송하기 전에 OPTIONS 메소드로 preflight를 전송한다.

이 때, ResponseAccess-Control-Allow-OriginAccess-Control-Allow-Methods가 넘어오는데 이는 서버에서 어떤 origin과 어떤 method를 허용하는지 브라우저에게 알려주는 역할을 한다.

브라우저가 결과를 성공적으로 확인하고 나면 가능한 origin들로부터 cross-origin 요청을 보내서 그 이후 과정을 진행한다.

Preflight 관련 설정

그렇다면, cross-origin 요청을 보낼 때마다 preflight 요청 작업을 거쳐야 하는가?! 그건 아니다.

서버 설정을 통해서 preflight 결과의 캐시를 일정 기간동안 저장시킬 수 있다.

이 캐시 정보가 살아있는 시간 동안은 preflight 를 생략하고 바로 요청 전송이 가능하다.

@CrossOrigin을 활용한 maxAge 설정

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    // maxAge=3600 설정을 통해 3600초 동안 preflight 결과를 캐시에 저장
    @CrossOrigin(origins="http://localhost:8080", maxAge=3600)
    @PostMapping("/")
    public String postSuccess() {
        return "REST API 호출 성공~!!";
    }
}

WebConfig maxAge 설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080")
                .maxAge(3600); // 3600초 동안 preflight 결과를 캐시에 저장
    }
}
profile
Developer

0개의 댓글