콘텐츠로 이동

2023 12 04

2023-12-04

CORS

참고: https://www.youtube.com/watch?v=-2TgkKYmJt4 참고: https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F#%F0%9F%A4%94_%EA%B7%B8%EB%9F%BC_%EC%A3%84%EB%8B%A4_%EC%B0%A8%EB%8B%A8%ED%95%98%EB%A9%B4_%EC%9D%B8%ED%84%B0%EB%84%B7%EC%9D%B4_%EB%90%98%EB%8A%94%EA%B0%80?

  • 개요
    • 교차 출처 리소스 공유(CORS)는 추가 HTTP 헤더를 사용하여
    • 한 출처에서 실행중인 웹 어플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제
  • 같은 출처란?
    • Protocol, Host, Port 가 다 같아야함
      • 여기서 host는 string으로 비교해서 localhost와 127.0.0.1 은 다른놈임
  • 접근 제어 시나리오 [Preflight]

    • 개요
      • 사전확인 작업 (Options)
      • Client <-> Server
    • Preflight Request
      • Origin: 요청 출처
      • Access-Control-Request-Method: 실제 요청의 메서드
      • Access-Control-Request-Headers: 실제 요청의 추가 헤더
    • Preflight Response
      • Access-Control-Allow-Origin: 서버 측 허가 출처
      • Access-Control-Allow-Methods: 서버 측 허가 메서드
      • Access-Control-Allow-Headers: 서버 측 허가 헤더
      • Access-Control-Max-Age: Preflight 응답 캐시 기간 (브라우저야 캐싱해둬)
    • 특징
      • 응답 코드는 200대일 것
      • 응답 바디는 비어있을 것
  • 접근 제어 시나리오 [Simple Request]
    • Preflight 요청 없이 바로 요청을 날림
    • 다음 조건을 모두 만족해야 Simple Request로 쓸 수 있음
      • GET/POST/HEAD 메서드일 것
      • Content-type은 하기의 작성된 것 중 하나일 것
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain
      • Header는 하기의 작성된 것 중 하나일 것
        • Accept
        • Accept-Language
        • Content-Language
        • Content-Type
  • 접근 제어 시나리오 [Credentialed Request]
    • 인증 관련 헤더를 포함할 때 사용하는 요청
    • 클라이언트 측
      • credentials: inclued
    • 서버 측
      • Access-Control-Allow-Credentials: true
  • 해결책 => 결국 CORS 해결책은 서버의 허용이 필요!
    • CORS 동작 과정을 살펴보면... 결국 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재해서 클라이언트에게 응답
    • 그러면 이제 클라이언트에서 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교해서 차단할지 말지를 결정
  • fetch API를 의심했었는데...
    • 어이. fetch는 믿어라.
    • 사실 CORS는 브라우저 정책이지, 서버끼리는 일어나지도 않고 그래.
    • 브라우저가 Origin 비교해서 야 꺼져 하는거란 말이야?
    • fetch도 post 쏘면 preflight 뚝딱 먼저 보낸다.

Play Framework에서 CORS

참고: https://www.playframework.com/documentation/2.8.x/CorsFilter#Enabling-the-CORS-filter 참고: https://www.playframework.com/documentation/2.8.x/resources/confs/filters-helpers/reference.conf 참고: https://coderunch.wordpress.com/2016/05/09/first-blog-post/ 참고: https://discuss.lightbend.com/t/cors-and-preflight-calls/225/10 참고: https://matthew.kr/%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-play-framework-2-4-angularjs-%EA%B0%84%EC%97%90-cors-%ED%95%84%ED%84%B0-%ED%97%88%EC%9A%A9%ED%95%98%EA%B8%B0/

  • 공식문서
    • Filter를 기반으로 CORSFilter를 활성화하려고 했는데... 아무리해도 안먹힘 (진짜 삽질 너무함)
      play.filters.enabled += "play.filters.cors.CORSFilter"
      play.filters.cors {
        pathPrefixes = ["/some/path", ...]
        allowedOrigins = ["http://www.example.com", ...]
        allowedHttpMethods = ["GET", "POST"]
        allowedHttpHeaders = ["Accept"]
        preflightMaxAge = 3 days
      }
      
  • 결국 CORS의 해결방법은 서버에서 http 헤더 적당한거 내려주는거야
    • 그러면 Action에서 내려주자.
    • 스프링으로 따지면 Interceptor에서 Response 처리해주는 역할
      override def invokeBlock[A](request: Request[A], block: Request[A] => Future[Result]): Future[Result] = {
          block(request).map { result =>
              result.withHeaders(
                  HeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN -> request.headers.get("Origin").getOrElse("*"),
                  HeaderNames.ACCESS_CONTROL_ALLOW_METHODS -> "POST, GET, OPTIONS",
                  HeaderNames.ACCESS_CONTROL_ALLOW_HEADERS -> "Origin, X-Requested-With, Content-Type, Accept, Referer, User-Agent",
                  HeaderNames.ACCESS_CONTROL_MAX_AGE -> Integer.toString(60 * 60 * 24)
              )
          }
      }