항해 5주차 WIL : CORS

백승한·2022년 7월 24일
0

CORS

CORS (Cross-Origin Resource Sharing)

  1. CORS란 웹 어플리케이션의 도메인이 다른 도메인의 리소스에 대해서 접근이 허용되는지 체크하는 매커니즘입니다. 웹 어플리케이션은 리소스를 요청하는 서버의 도메인, 프로토콜 또는 포트가 다를 경우, cross-origin HTTP request 요청을 실행합니다.

  2. 다른 출처의 자원을 공유할 수 있도록 설정하는 권한 체제를 말합니다.
    따라서 CORS를 설정해주지 않거나 제대로 설정하지 않은 경우, 원하는대로 리소스를 공유하지 못하게 됩니다.

*여기서 출처는 '프로토콜://도메인:포트' 이다. 예를 들자면 http://localhost:8080

보안상의 이유로, 브라우저는 cross-origin HTTP request에 대해서 same-origin policy를 적용하여 동작합니다. 즉, a.com 이라는 도메인의 클라이언트에서 리소스를 요청할 때는 a.com 이라는 도메인의 서버일 경우에 CORS 문제가 발생하지 않고 정상적으로 동작합니다. 만약 두 도메인이 서로 다르다면, CORS에 대해 Header 설정을 해주어야 cross-origin HTTP request 에 대해서 정상적으로 요청과 응답이 이루어집니다.

요청 헤더 목록

  • 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 요청의 종류

  • Simple request
  • Preflight request

Simgple request
Simple request 는 Preflight 체크를 하지 않으며, 클라이언트와 서버간에 한 번만 요청과 응답을 주고 받습니다. Simple request 는 아래의 조건들을 만족하면 요청하게 됩니다.

  1. 요청 메서드
    - GET
    - HEAD
    - POST

  2. 커스텀 헤더를 전송을 하지 말아야 한다.

  3. 허용되는 헤더는 다음과 같다.
    - Accept
    - Accept-Language
    - Content-Language
    - Content-Type
    - Last-Event-ID
    - DPR
    - Save-Data
    - Viewport-Width
    - Width

  4. Content-Type 헤더의 허용되는 Value 값은 다음과 같다.
    - application/x-www-form-urlencoded
    - multipart/form-data
    - text/plain

Preflight request
Simple request 의 조건에 만족하지 않으면 Preflight request 방식으로 요청합니다. Preflight request 는 다른 도메인에 HTTP request 를 전송하기 전에 OPTIONS 메서드로 사전 요청을 통해 서버로부터 안전한 요청인지 응답을 받고, 본 요청을 수행합니다.

CORS의 동작 방식

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

Spring Security 에서 CORS 문제에 대해서 간단하게 해결하는 방법입니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsServiceImpl userDetailsService;
    private final JwtProvider jwtProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                
                ...
                
                .and()
                .cors()
                .and()...;
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.addAllowedOrigin("http://localhost:3000");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

클라이언트는 axios 를 통해서 간단하게 로그인을 위한 userId 와 password 데이터를 POST 방식으로 전송하고 있습니다.

<script>
    import axios from 'axios'

    export default {
        name: "Login",
        data() {
            return {
                form: {
                    userId: '',
                    password: ''
                }
            }
        },
        methods: {
            tryLogin() {
                axios.post('auth/login', {
                    userId: this.form.userId,
                    password: this.form.password
                })
                    .then((res) => {
                        console.log(res.data);
                    })
                    .catch((e) => {
                        console.error(e);
                    })
            }
        }
    }
</script>

CORS헤더

요청의 Content-Type 이 application/json 이므로, Preflight request 가 발생하여 OPTIONS 메서드로 요청을 보냅니다.

Preflight request 요청으로 200 코드를 받고나서 POST로 본 요청을 수행하여 로그인 요청을 수행합니다.

간단 요약

서로 다른 도메인의 리소스 요청을 보내고 받기 위해서는 웹프론트엔드와 서버에서 특정한 작업을 해주어야하는데
프론트의 경우 Request Header에 CORS 관련 옵션을 넣어주는 것이고
서버의 경우에는 Response Header에 해당하는 프론트의 요청을 허용한다는 내용을 넣어주면 된다고 합니다. 위의 Header에 뭘 넣어야 할지는 정확하게 정해진 내용이 있고 그에 따라 넣어주기만 하면된다고 합니다!

특별한점은 HTTP Option 메소드입니다. 크로스 도메인 요청을 보내는 작업은 서버로 요청을 한 번만 보내는게 아니라고합니다 사용할때 딱 한 번 부르는 것처럼 보이지만 실제로는 서버에 요청을 두 번 보내게 된다고 합니다.

위의 사진처럼 먼저 서버에 HTTP OPTION 메소드를 이용해 요청을 허용해 줄건지 물어보고 허용이된다고 하면 그때 GET 이나 POST 메소드를 가지고 실제 요청을 보내고 결과를 받는 것이라고 합니다.

위의 사진에 빨간 박스처럼 Access - Control - Allow 가 있다면 OPTION 메소드라고 합니다.

그래서 서버에서도 웹브라우저로부터 크로스 도메인 요청을 받으려면 동일한 라우트에 대해서 OPTION 메소드 처리를 따로 하도록 작업을 해주어야 하지만 보통은 서버 라이브러리에서 간편하게 알아서 해주기 때문에 직접 신경써야 할 일은 거의 없을 것이라고 합니다!

이런 기능들을 잘 활용한다면 서버에서는 특정 라우트만 CORS 요청을 허용하게끔 하거나 혹은 특정 도메인에서 오는 요청만 허용하도록 할 수도 있을 겁니다!

참고

https://icarus8050.tistory.com/28

https://velog.io/@seho100/CORS%EB%9E%80-Spring-boot%EC%97%90%EC%84%9C%EC%9D%98-CORS-Preflight%EC%97%90-%EA%B4%80%ED%95%9C-%EC%9D%B4%EC%8A%88

https://velog.io/@dorazi/CORS

https://www.youtube.com/watch?v=yTzAjidyyqs

https://hanamon.kr/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-http-options-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0%EC%99%80-cors%EB%9E%80/

profile
방문해주셔서 감사합니다🙂

0개의 댓글