레이블이 CORS인 게시물을 표시합니다. 모든 게시물 표시
레이블이 CORS인 게시물을 표시합니다. 모든 게시물 표시

js fetch() with credntial 옵션 사용시 CORS 에러 방지 서버 설정

# javascript(js) fetch 함수 요청시 CORS(Cross-Origin Resource Sharing) 에러 방지하기
# js fetch() 로 ysoftman1 에서 ysoftman2 로 요청하는 상황으로 다음과 같다.
# 크롬 개발자 도구 console 에서 실행
fetch("http://ysoftman2/lemon", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "authorization": "Bearer aaabbbccc123456789",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://ysoftman1/apple",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
}).then(response => response.json());

# 이때 요청 흐름은 js fetch() --> browser --> server 이 된다.
# browser 는 OPTIONS 메소드로 서버에게 접근 가능한지 물어본다.(preflight 과정)
# 이때 서버는 응답 헤더에 다음 값들을 설정할 수 있고
Access-Control-Allow-Origin: 접근 가능한 호스트
Access-Control-Allow-Methods: 접근 가능한 메소드
Access-Control-Allow-Headers: 접근 가능한 헤더

# browser 는 이 허용된 값들 내에서 서버에 실제 요청을 하게 된다.

# 서버가 와일드카드(Access-Control-Allow-Origin: *)를 사용하더라도 
# 클라가 credential (ajax, xmlhttp 로 다른도메인에 쿠키를 설정할때 사용하는 옵션)를 사용하면 CORS 정책으로 블럭된다.
# 다음과 같이 exact 한 호스명과 vary (브라우저가 캐시 사용시 어떤 헤더를 보고 구분해야 하는지 알려준다) 헤더에 origin 을 설정해야 한다.
Access-Control-Allow-Origin: https://developer.mozilla.org
Vary: Origin

# 서버쪽 설정 방법들
# 방법1 - k8s ingress 사용시, enable-cors true 설정(디폴트 false)
# enable-cors 를 사용하면 cors 관련 응답헤더들(Access-control-xxx)이 자동으로 추가된다.
# 그리고 Access-Control-Allow-Origin 가 * 로 고정된다.
# configuration-snippet 의 Access-Control-xxx 헤더가 응답에 중복되어
# cors 가 동작하지 않으니 같이 사용하지 않는다.
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-headers: "x-test-header"
    
# 방법2 - k8s ingress 사용시, configuration-snippet 로 설정
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # configuration-snippet 현재 location 의 설정에 추가된다.
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        add_header 'Access-Control-Max-Age' "1728000";
        add_header 'Content-Type' "text/plain charset=UTF-8";
        add_header 'Content-Length' "0";
        add_header 'Vary' "Origin";
        return 204;
      }
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Credentials' "true";
      add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
      add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
      add_header 'Vary' "Origin";

# 방법3 - k8s ingress 사용시, 특정 도메인에만 cors 허용할때 변수를 설정해 구분
metadata
  annotations
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($http_origin ~* "^https?:\/\/(.*\.)?((ysoftman\-lemon\.com)|(ysoftman\-apple\.com))$") {
        set $cors = "cors";
      }
      if ($request_method = 'OPTIONS') {
        set $cors = "cors_options";
      }
      if ($cors = "cors_options") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 200;
      }
      if ($cors = "cors") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 204;
      }

# 방법4 - 서버에 직접 설정하는 경우
# 아래는 go chi 핸들러 사용하는 예시
func CORSHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    // CORS(Cross-Origin Resource Sharing) 에러 방지 헤더 추가
    if r.Method == http.MethodOptions {
      // w.Header().Set("Access-Control-Allow-Origin", "*")
      // js fetch() credentials 옵션 사용시 와일드카드(*) 대신 요청 origin 으로 설정해야 한다.
      w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
      w.Header().Set("Access-Control-Allow-Credentials", "true")
      w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
      w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
      // options (preflight) 일때는 CORS 에러 방지 헤더 추가하여 바로 응답을 줘 클라가 다시 요청을 시도할 수 있도록 한다.
      return
    }
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    next.ServeHTTP(w, r)
  }
  return http.HandlerFunc(fn)
}

# 주의사항
# 만약 k8s ingress 과 서버내 양쪽에서 모두 access-controll-allow 헤더들을 설정하면 중복 설정되고 CORS 정책에 위배돼 에러가 발생한다.
The 'Access-Control-Allow-Origin' header contains multiple values 'https://....', 
but only one is allowed. 
Have the server send the header with a valid value, or, 
if an opaque response serves your needs, 
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

# 그래서 한쪽에서만 설정해줘야 한다.
# 참고로 curl 로 cors 확인(access-control-xxx 헤더)을 위해선 다음 옵션을 사용해야 한다.
curl -X GET "https://ysoftman.lemon.com/aaa/bbb?ccc=lemon" \
-H "accept: application/json" \
--header 'Origin: http://ysoftman.lemon.com' \
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET'

JavaScript No Access-Control-Allow-Origin 에러 해결하기

# 보안상의 이유로 ajax(XMLHttpRequest) 로 요청하는 도메인과 요청받는 도메인이
# 다를 경우(크로스 도메인) 브라우저 개발 콘솔창에서 다음 에러메시지를 확인 할 수 있다.
"No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. "
# Firefox 로 보면
# CORS(cross origin resource sharing), same origin(동일 출처) 원칙에 따르면 현재 웹페이지의 리소스(스크립트, 이미지, 동영상등)의 출처가 현재 도메인과 다르면 안되지만 이를 가능하게 하는 메커니즘이다.

"교차 원본 요청 차단: 동일 출처 정책으로 인해 http://10.10.10.10/ysoftman 에 있는 원격 자원을 읽을 수 없습니다. 자원을 같은 도메인으로 이동시키거나 CORS를 활성화하여 해결할 수 있습니다."

# 예를 들어 로컬 디스크에서 개발중인 ajax내용이 있는 .html파일 크롬으로 실행시키면 발생할 수 있다.

# 내 PC 도메인은 file:///C:어쩌구저쩌구... 인데 실제 ajax 로 요청하는 호스트 도메인은 http://10.10.10.10 등으로 서로 다르기 때문이다.
# 서버단에서 해결방법
# 서버쪽에서 응답 헤더에 Access-Control-Allow-Origin 값을 모두 허용하도록 설정
# 서버 언어에 맞게 응답 헤더에 설정하도록 한다.
header("Access-Control-Allow-Origin", "*");
header("Access-Control-Allow-Origin: *");

# 클라(브러우저)에서 해결방법
# 크롬의 경우 현재 열려진 모든 크롬을 닫고 아래 옵션을 주어 실행
chrome --disable-web-security

# 아래 크롬 주소창로 크롬 실행시 어떤 옵션으로 실행되었는지 파악할 수 있다.
chrome://version

# IE의 경우
# 요청 후 나타나는 "차단된 콘텐츠 허용" -> 예 선택
# 외부 URL 요청이라면 웹브라우저가 preflight 요청을 수행한다.
# preflight 는 method=options 로 설정하고 해당 URL 로 미리 요청을 날려 요청이 허용(allow) 되는지 파악하는 과정이다.
# preflight 요청시에 다음과 같이 헤더 설정을 한다.
# POST 메소드를 사용할것이고 X-PINGOTHER 헤더가 포함될것이라고 알린다.
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

# preflight (method=options) 요청후 서버 응답으로 서버에서 사용가능한 리소스, 메소드, 헤더등의 정보를 알려준다.
# http://foo.example 도메인에서만 허용된다.
# POST, GET, OPTIONS 메소드 허용된다.
# 86400 초 동안 preflight 응답을 캐싱
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

# preflight 응답이 다음과 같다면 모든 도메인에서 접근(요청)이 가능하다.
Access-Control-Allow-Origin: *

# 참고