API호출을 하면서 발생할 수 있는 문제중 하나로 CORS 관련 문제가 있다. CORS에 대한 개념 뿐만아니라 안전하게 설정하고 테스트하는 방법을 알고 있어야한다.
CORS에 대한 개념 및 DRF에서 설정하는 방법에 대해서는 여러 공식문서 외에도 블로그 글로 잘 정리되어 있었는데, 잘 적용되었는지 테스트하는 방법에 대해서는 자세히 안나와 있고, 프로젝트를하면서 cors에러가 발생해서 어떻게해서 해결했다. 라는 글만 있어서 직접 삽질하면서 알아낸 방법에 대해서도 이야기 해보려고한다.
CORS란?
개념에 관해서는 자세히 정리된 글이 있어 굳이 따로 정리하진 않았지만 CORS를 간단히 설명하자면, 웹 페이지가 다른 도메인의 리소스에 접근할 수 있도록 허용하는 보안 기능이다. 브라우저는 보안 상의 이유로 스크립트에서 시작된 크로스오리진 HTTP요청을 제한한다. 서버에서는 이러한 요청들을 허용하기 위해 적절한 CORS헤더를 포함한 응답을 반환해야한다.
Django에서 CORS 설정하기
패키지 설치
django에서 CORS는 패키지만 설치하고 설정해주면 간단한 문제이다.
pip install django-cors-headers
설정 파일 수정
주의해야할 점으로는 MIDDLEWARE에 corsheaders.middleware.CorsMiddleware가 맨 위에 와야하고, CORS_ORIGIN_ALLOW_ALL = True로 설정해줄 경우 CORS_ORIGIN_WHITELIST는 설정해주지 않아도 된다.
INSTALLED_APPS = [
...,
'corsheaders',
...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...,
]
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000',
]
테스트
처음에는 CORS확인을 위한 페이지도 찾아보고, 로컬에서 서버도 돌려보면서 진행하였는데 XMLHttpRequest요청이 로컬에서 호스팅되는 자원에 대한 요청을 할려고할때, 보안 컨텍스트가 아닐경우 막힐 수 있어 https://를 사용하야하는 것이였다.
문제를 해결하기위해서 django-sslserver나 mkcert를 이용해서 SSL인증서를 생성하거나, 배포를하여 확인해 볼 수도 있으나 이래저래 제약사항이 많았고 결국 아래의 방법을 선택하였다.
Postman을 이용해서 오리진 정보를 바꿔서 서버에 요청을 보내고 응답을 확인하면서 해결할 수 도 있지만 curl 커맨드 라인을 이용하는 방법이 쉽고 간편할 것 같아서 커맨드라인을 이용해서 진행하였다.
어려운 방법이 아니라 터미널창을 열어서 아래의 명령어를 그대로 입력해주면 된다. 요청 정보를 바꾸고싶으면 오리진 정보를 바꾸면 된다. 나의 경우는 "http://localhost:3000"로 진행하였다.
curl -H "Origin: http://example.com" \
-H "Access-Control-Request-Method: POST" \
-X OPTIONS --verbose \
http://localhost:8000/admin
천천히 한줄씩 설명하자면
- -H "Origin: http://localhost:3000": 요청이 http://localhost:3000에서 발생했다고 서버에 알려준다. 이는 CORS 정책 검증에 사용된다.
- -H "Access-Control-Request-Method: POST": 실제 요청에서 POST 메소드를 사용할 것임을 서버에 알리는 데 사용된다.. 이는 서버가 OPTIONS 프리플라이트 요청을 받았을 때, POST 요청을 허용할지 결정하는데 사용된다.
- -X OPTIONS: 이 옵션은 curl이 OPTIONS 메소드를 사용하여 요청을 보내도록 지시한다. OPTIONS 요청은 서버가 특정 오리진에서 특정 HTTP 메소드와 헤더를 사용할 수 있는지 확인하기 위한 용도로 사용된다.
- --verbose: 이 옵션은 요청과 응답에 대한 자세한 정보를 출력하도록 한다. 헤더, 상태 코드 등의 세부적인 정보를 확인할 수 있다.
결과는 아래와 같이 나왔다.
- * Trying [::1]:8000...: curl이 localhost의 IPv6 주소 [::1] 포트 8000에 연결을 시도합니다.
- * connect to ::1 port 8000 failed: Connection refused: IPv6 주소 [::1]로의 연결이 실패하였습니다. 이는 해당 주소에서 서버가 실행되고 있지 않거나 연결을 허용하지 않기 때문일 수 있습니다.
- * Trying 127.0.0.1:8000...: 연결 시도가 실패하자 curl은 localhost의 IPv4 주소 127.0.0.1 포트 8000에 연결을 시도합니다.
- * Connected to localhost (127.0.0.1) port 8000: IPv4 주소 127.0.0.1에 성공적으로 연결되었습니다.
- > OPTIONS /admin HTTP/1.1: curl은 /admin 경로로 OPTIONS HTTP 메소드를 사용하여 요청을 보냅니다.
- > Host: localhost:8000: 요청의 호스트 헤더가 localhost:8000으로 설정됩니다.
- > User-Agent: curl/8.4.0: 요청을 보내는 클라이언트는 curl 버전 8.4.0입니다.
- > Accept: */*: 이 헤더는 클라이언트가 모든 종류의 미디어 타입을 수락함을 나타냅니다.
- > Origin: http://localhost:3000: 요청이 http://localhost:3000에서 발생했음을 나타내는 Origin 헤더입니다.
- > Access-Control-Request-Method: POST: 실제 요청이 이루어질 때 POST 메소드 사용을 서버에 사전 알림합니다.
- < HTTP/1.1 200 OK: 서버가 요청을 성공적으로 처리했으며 HTTP 상태 코드 200 (OK)을 반환합니다.
- < Date: Tue, 14 May 2024 00:53:14 GMT: 응답이 생성된 날짜와 시간입니다.
- < Server: WSGIServer/0.2 CPython/3.11.6: 서버는 Python 기반의 WSGI 서버, 버전 0.2, Python 3.11.6을 사용하고 있습니다.
- < content-length: 0: 응답 본문의 길이가 0이며, 본문 내용이 없음을 나타냅니다.
- < Content-Type: text/html; charset=utf-8: 응답의 콘텐츠 유형은 HTML이며, 문자 인코딩은 UTF-8입니다.
- < Vary: origin: 응답이 요청의 Origin 헤더에 따라 달라질 수 있음을 나타냅니다.
- < access-control-allow-origin: http://localhost:3000: 서버가 http://localhost:3000 오리진으로부터의 요청을 허용함을 나타냅니다.
- < access-control-allow-credentials: true: 크로스-오리진 요청에서도 사용자의 자격 증명(쿠키, HTTP 인증 등)을 허용합니다.
- < access-control-allow-headers: accept, authorization, content-type, user-agent, x-csrftoken, x-requested-with: 이 헤더는 서버가 허용하는 HTTP 요청 헤더 목록을 나타냅니다.
- < access-control-allow-methods: DELETE, GET, OPTIONS, PATCH, POST, PUT: 서버가 허용하는 HTTP 메소드 목록을 나타냅니다.
- < access-control-max-age: 86400: 브라우저가 프리플라이트 요청의 결과를 최대 86400초(하루) 동안 캐시할 수 있음을 나타냅니다.
- * Connection #0 to host localhost left intact: 연결이 유지된 상태임을 나타내며 추가적인 요청을 위해 연결을 재사용할 수 있습니다.
'BackEnd > Django, DRF' 카테고리의 다른 글
[Custom] Django와 DRF에서 커스텀 예외처리하기(custom_exception) (0) | 2024.05.16 |
---|---|
[TEST] 테스트 DB 없이 Django에서 유닛 테스트 수행하기 (0) | 2024.05.14 |
[Custom] DRF Custom exception fomat (exception_handler) (0) | 2024.05.02 |
[Custom] DRF Custom Response fomat (Response) (1) | 2024.05.01 |
[Custom] DRF Custom Render fomat (JSONRenderer) (0) | 2024.04.29 |