CORS pre-flight(예비 요청)이란?

Jang Seowoo·2023년 1월 11일
5

프로젝트 디버깅

목록 보기
2/2

문제

Access-Control-Request-Headers에 들어가버린 header(authorization)에 들어있는 값을 어떻게 빼내오죠..?

문제 사항은 다음과 같았다.

나는 Springboot와 Flutter를 연결하는 작업을 하고 있었고, Springboot Security를 사용하여 authenticated 옵션을 걸어놨었다.

이렇게 authenticated가 걸려있는 주소로 flutter에서 get을 요청할 때 authorization을 넣어서 보내줘야하는데 자꾸만 제대로 된 값을 읽어오지 못하는 것이었다.

  • flutter 코드
Uri getUri = Uri.parse("http://localhost:8080/api/parking/data");

final response = await http.get(getUri, headers: {
    HttpHeaders.authorizationHeader: _userProvider.token,
    'content-type': 'application/json'
});

if (response.statusCode != 200) {
    return false;
}
  • 실패화면1
    실패화면1
    Securing OPTIONS /api/parking/data
    ...
    Failed to authorize filter invocation [OPTIONS /api/parking/data] with attributes [authenticated]
  • 실패화면2
    실패화면2
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: authorization, content-type

원인은 springboot security에서 설정했던 authenticated attributes 를 가진 OPTIONS /api/parking/data가 filter invocation을 authorize하는 데에 실패했다는 말이다.

처음엔 이게 뭔 말인지조차 이해할 수 없었다. 난 GET으로 보냈는데 OPTIONS는 뭔데..? Authorization이 왜 헤더에 있지 않고 Access-Control-Request-Headers 안으로 들어간 거지? 난 authorization을 헤더로 넣어줬는데?

그래서 열심히 구글링 해본 결과, 우선 Access-Control-Request-Headers 요청 헤더는 브라우저에서 prefligt 요청을 실행할 때 '서버에 실제 요청이 있을 때 클라이언트가 보낼 수 있는 http 헤더'를 알려주는 데에 사용된다고 한다. preflight 요청은 cors 요청 시에 특정 조건이 맞지 않는 경우에 먼저 OPTIONS 방식으로 보낸 이후에 다시 정상 request를 날린다고 한다. 즉, 나의 문제는 cors 요청에서 어떤 조건에 걸려서 preflight로 먼저 요청이 가는데 이 요청이 막힌 것이 문제였다.

정확하게 preflight가 뭘까?

그 전에 간단하게 CORS를 짚고 넘어가보자.

CORS란?

CORS는 Cross-Origin Resource Sharing의 약어로 다른 출처의 자원을 공유하는 것이다.

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 Cross-Origin HTTP 요청을 실행한다.

CORS

Pre-flight란?

이 CORS HTTP(s) 요청에는 두가지 요청 방식이 있다. 조건과 함께 살펴보자.

첫번째, Simple Request(단순 요청)이다.

  1. GET, POST, HEAD 요청이어야 한다.
  2. 유저 에이전트가 자동으로 설정한 헤더외에, 수동으로 설정할 수 있는 헤더는 Accept, Accept-Language, Content-Language, Content-Type 뿐이다.
  3. Content-Type 헤더는 application/x-www-form-urlencoded, text/plain, multipart/form-data 이어야 한다.
  4. 요청에 사용된 XMLHttpRequestUpload 객체에는 이벤트 리스너가 등록되어 있지 않아야 한다.
  5. 요청에 ReadableStream 객체가 사용되지 않아야 한다.

두번째, Preflight Request(프리플라이트 요청)이다.

  1. PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 방식을 사용하는 경우
  2. 금지된 헤더 이름을 사용하는 경우 - https://fetch.spec.whatwg.org/#forbidden-header-name
  3. 요청 페이로드 유형이 text/plain, multipart/form-data 또는 application/x-www-form-urlencoded 이 아닌 경우.
  4. 하나 이상의 이벤트 리스너가 XMLHttpRequestUpload에 등록되어있는 경우

따라서 나의 경우에서는 Content-type을 application/json을 썼고, authorization 헤더 즉, 사용자 정의 헤더가 설정되었기 때문에 요청이 preflighted 처리가 된 것이다.

왜 Pre-flight?

"preflighted" request는 위에서 논의한 “simple requests” 와는 달리, 먼저 OPTIONS 메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인한다. cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와같이 미리 전송(preflighted)합니다.

드디어 해결!

따라서 나의 해결방법은 Springboot에서 preflight로 들어오는 요청들을 permitAll 할 수 있도록 바꾸었다.

.antMatchers(HttpMethod.OPTIONS, "/**/*").permitAll()

위와 같이 코드를 security에 추가해주고 성공하면 다음과 같이 화면이 뜬다.

성공화면2

본 요청에 들어간 Authorization 헤더를 보여주고 결과 값으로 토큰을 성공적으로 불러온다.


참고자료

profile
https://devseowoo.notion.site/Seowoo-Portfolio-b21365c3477345818913e8d8fe2e3b90

0개의 댓글