CS/์ธํ”„๋ผ

[web] CORS (Cross Origin Resource Sharing)

๊น€yejin 2022. 8. 27. 00:08

 

๐Ÿค” ๋ชฉ์ฐจ
  1. CORS ๋ž€
  2. CORS ์š”์ฒญ(Request) ์˜ ์ข…๋ฅ˜
    1. Simple Request
    2. Preflight Request
    3. Request with Credential
    4. Request without Credential
  3. Server์—์„œ CORS ์š”์ฒญ ํ•ธ๋“ค๋งํ•˜๋Š” ๋ฐฉ๋ฒ•
  4. ํ…Œ์ŠคํŠธ
  5. ์ •๋ฆฌ

 

CORS ๋ž€


Cross-Origin Resource Sharing

์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™ธ๋ถ€ ๋„๋ฉ”์ธ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ์‹์„ ํ‘œ์ค€ํ™”ํ•œ specification

  • ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ •ํ•ด์ง„ ํ—ค๋”๋ฅผ ํ†ตํ•ด ์„œ๋กœ ์š”์ฒญ์ด๋‚˜ ์‘๋‹ต์— ๋ฐ˜์‘ํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ์‹
  • ex) header๊ฐ€ “” ์ผ๋•Œ, ์„œ๋ฒ„๋Š” ์š”์ฒญ์— ๋ฐ˜์‘ ~, ํด๋ผ์ด์–ธํŠธ๋Š” ์‘๋‹ต์— ๋ฐ˜์‘ ~

<aside> โ“ What is Same Origin Policy

</aside>

Same Origin Policy

์›น ๋ณด์•ˆ ์ •์ฑ… ์ค‘ ํ•˜๋‚˜๋กœ, ํ•œ ์ถœ์ฒ˜(origin) ์—์„œ ๋กœ๋“œ๋œ ๋ฌธ์„œ๋‚˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋‹ค๋ฅธ ์ถœ์ฒ˜ ์ž์›๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ œ์•ฝํ•œ๋‹ค.

→ ๊ฐœ์„ ๋œ ์ •์ฑ… : CORS

Same Origin ์ด๋ž€

๋‘ ํŽ˜์ด์ง€์˜ Protocol, Host, Port ๊ฐ€ ๊ฐ™์€ ๊ฒฝ์šฐ

ex) js์—์„œ ajax๋กœ resource load ํ• ๋•Œ

origin : http://aaa.yejin.com/dir/index.html

URL ๊ฒฐ๊ณผ ์ด์œ 

http://aaa.yejin.com/dir/test.html o  
http://aaa.yejin.com/dir/inner/test.html o  
https://aaa.yejin.com/index.html x Protocol X
http://aaa.yejin.com:8888/dir/index.html x Port X
http://bbb.yejin.com/dir/index.html x Host X

CORS ์š”์ฒญ(Request) ์˜ ์ข…๋ฅ˜


Simple Request

์กฐ๊ฑด

  1. GET,HEAD,POST ์ค‘ ํ•œ๊ฐ€์ง€ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
  2. POST ๋ฐฉ์‹์ผ ๊ฒฝ์šฐ Content-type์ด ์•„๋ž˜ ์…‹ ์ค‘์— ํ•˜๋‚˜์—ฌ์•ผ ํ•œ๋‹ค.
    1. application/x-www-form-urlencoded
    2. multipart/form-data
    3. text/plain (default type)
  3. Custom header๋ฅผ ์ „์†กํ•˜์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค.

์˜ˆ์‹œ

๋ธŒ๋ผ์šฐ์ € url ํ˜ธ์ถœ : http://domainA

domainA → ajax์—์„œ domainB ์—์„œ resource๋ฅผ ๊ฐ€์ ธ์˜ด

์ฆ‰, domainA (origin : domainA) ์—์„œ resourceB (host : domainB) ๋ฅผ ํ˜ธ์ถœ

Request#1 // ์š”์ฒญ 1
GET path HTTP/1.1
Host : <http://domainB>
Origin : <http://domainA>

Response#1 // ์„œ๋ฒ„์˜ ํšŒ์‹  1 origin : domainA
HTTP/1.1 200 OK
Access-Control-Allow-Origin : * // ์„œ๋ฒ„์—์„œ ์ด ํ—ค๋”์˜ ๊ฐ’์„ ๋ณด๊ณ  ๊ฒฐ์ •

Preflight Request

Simple Request ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜์ง€ ์•Š์„ ์‹œ, ๋ธŒ๋ผ์šฐ์ €๋Š” Preflight Request ๋ฐฉ์‹์œผ๋กœ ์š”์ฒญ

์กฐ๊ฑด

  1. GET,HEAD,POST ์™ธ์˜ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์š”์ฒญ ๊ฐ€๋Šฅ
  2. ๋‹ค๋ฅธ Content-type์œผ๋กœ (application/xml ๋“ฑ) ์š”์ฒญ ๊ฐ€๋Šฅ
  3. Custom header ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์š”์ฒญ ๋ฐฉ๋ฒ•

  1. Preflight (์˜ˆ๋น„ ์š”์ฒญ)์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
  2. Request#1 // preflight OPTIONS path HTTP/1.1 Host : <http://domainB> Origin : <http://domainA> Response#1 // prefilght ์‘๋‹ต HTTP/1.1 200 OK Access-Control-Allow-Origin : <http://domainA> // domainA๊ฐ€ ์ถœ์ฒ˜(domainB) ์“ฐ๋Š” ๊ฒƒ allow Access-Contorl-Allow-Methods : POST,GET,OPTIONS
  3. ์‹ค์ œ๋กœ ์š”์ฒญํ•˜๋ ค๋Š” ๊ฒฝ๋กœ์™€ ๊ฐ™์€ URL์— ๋Œ€ํ•ด OPTION ๋ฉ”์†Œ๋“œ๋กœ ์š”์ฒญ์„ ๋ฏธ๋ฆฌ ๋‚ ๋ ค ๋ณธ ํ›„,
  4. ์‹ค์ œ ์š”์ฒญ
  5. Request#2 // ์‹ค์ œ์š”์ฒญ POST path HTTP/1.1 Host : <http://domainB> Origin : <http://domainA> Respons#2 HTTP/1.1 200 OK Access-Control-Allow-Origin : <http://domainA>

Request with Credential

HTTP Cookie์™€ HTTP Authentication ์ •๋ณด๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์š”์ฒญ

protocol, port ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋  ๊ฒฝ์šฐ, session์ด ์œ ์‹ค ๋˜์ง€ ์•Š๊ฒŒ ํ•˜๋Š” ์กฐ์น˜

ํด๋ผ์ด์–ธํŠธ ๋‹จ
xhr.withCredential = true 

์„œ๋ฒ„ ๋‹จ
Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin : <http://domainA> // * ์•ˆ๋จ
// Request ajax ๋ถ€๋ถ„
$.ajax({
	type : ,
	contentType: ,
	url: ,
	success:function(){
	},
	error:function(){
	},
	xhrFields:{
		withCredentials:true // credential ์š”์ฒญ์„ ์ฃผ์–ด์•ผ ํ•จ
	},
});

Request without Credential

default๋กœ Non-Credential ์š”์ฒญ

( xhr.withCredentials=true ์‹œ์—๋งŒ credential ์š”์ฒญ)

 

Server์—์„œ CORS ์š”์ฒญ ํ•ธ๋“ค๋งํ•˜๋Š” ๋ฐฉ๋ฒ•


EX1) Preflight Request

//OPTION ๋ฉ”์„œ๋“œ์˜ ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์ปจํŠธ๋กค

//header ์˜ˆ์‹œ
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,DELETE,OPTIONS
Access-Control-Max-Age:3600
Access-Control-Allow-Header: Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization (*๋กœ ํ—ˆ์šฉ์•ˆ๋จ)

Ex2) Request with Credential

// With Credential
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin : ๋„๋ฉ”์ธ๋ช… // * ์•ˆ๋จ

 

ํ…Œ์ŠคํŠธ


โš ๏ธ CORS error

host ๊ฐ€ ๋‹ค๋ฅธ ์š”์ฒญ

$('#cors1__err').click(function() {
    	console.log('host diff');
		$.ajax({
			url : '<http://bbb.yejin.co.kr/DynamicWeb03/index.jsp>',
			success : function() {
				alert('success');
			},
			error : function() {
				alert('error');
			}
     	})
});

Access to XMLHttpRequest 
at '<http://bbb.yejin.co.kr/DynamicWeb03/index.jsp>' 
from origin '<http://aaa.yejin.co.kr>' 
has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.
// Request Headers
GET /DynamicWeb03/index.jsp HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
Host: bbb.yejin.co.kr
Origin: <http://aaa.yejin.co.kr>
Referer: <http://aaa.yejin.co.kr/>
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36

// Response Headers
HTTP/1.1 200
Set-Cookie: JSESSIONID=0C4002012D3D81EA3F5044992558C040; Path=/DynamicWeb03; HttpOnly
Content-Type: text/html;charset=EUC-KR
Content-Length: 239
Date: Wed, 13 Jul 2022 05:12:54 GMT

 

Protocol http → https

$('#cors2__err').click(function() {
    	console.log('protocol diff');
		$.ajax({
			url : '<https://aaa.yejin.co.kr/DynamicWeb01/index.jsp>',
			success : function() {
				alert('success');
			},
			error : function() {
				alert('error');
			}
     	})
});

Access to XMLHttpRequest 
at '<https://aaa.yejin.co.kr/DynamicWeb01/index.jsp>' 
from origin '<http://aaa.yejin.co.kr>' 
has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

Protocol https → http : Mixed Content (It is not CORS)

Mixed Content: 
The page at '<https://aaa.yejin.co.kr/DynamicWeb01/index.jsp>' 
was loaded over HTTPS, 
but requested an insecure XMLHttpRequest endpoint 
'<http://bbb.yejin.co.kr/DynamicWeb03/index.jsp>'. 
This request has been blocked; the content must be served over HTTPS.

 

Port ๊ฐ€ ๋‹ค๋ฅธ ์š”์ฒญ

$('#cors3__err').click(function() {
    	console.log('port diff');
		$.ajax({
			url : '<http://aaa.yejin.co.kr:8080/DynamicWeb01/index.jsp>',
			success : function() {
				alert('success');
			},
			error : function() {
				alert('error');
			}
     	})
});

Access to XMLHttpRequest 
at '<http://aaa.yejin.co.kr:8080/DynamicWeb01/index.jsp>' 
from origin '<http://aaa.yejin.co.kr>' 
has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

๐Ÿ†— Acess-Control-Allow-Origin

// Web ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์˜ Response Header์— ์•„๋ž˜ ๋‚ด์šฉ ์ถ”๊ฐ€

cors_all	      Action = AddResponse,
                FieldName = "Access-Control-Allow-Origin",
								FieldValue = "*"

// Request Header
GET /DynamicWeb02/index.jsp HTTP/1.1
...
Host: bbb.yejin.co.kr
Origin: <http://aaa.yejin.co.kr>
Referer: <http://aaa.yejin.co.kr/>
...

// Response Header
HTTP/1.1 200
Access-Control-Allow-Origin: *
Set-Cookie: JSESSIONID=28C41758BF3E387AB88E616A3F55631A; Path=/DynamicWeb02; HttpOnly
Content-Type: text/html;charset=EUC-KR
Content-Length: 424
Date: Wed, 13 Jul 2022 05:51:52 GMT

 

with Credential → ์„ธ์…˜ ์œ ์ง€ ํ…Œ์ŠคํŠธ

// origin 
JSESSIONID=EE1C98820B6693EF2E710B74697B8074

// no credential -> session ์žฌ๋ฐœ๊ธ‰
HTTP/1.1 200
Access-Control-Allow-Origin: *
Set-Cookie: JSESSIONID=A21D1937053DE17F3C29B91FEF4D0B78; Path=/DynamicWeb01; HttpOnly
Content-Type: text/html;charset=EUC-KR
Content-Length: 424
Date: Wed, 13 Jul 2022 06:12:13 GMT

// credential -> session ์œ ์ง€
HTTP/1.1 200
Access-Control-Allow-Origin: <http://aaa.yejin.co.kr>
Access-Control-Allow-Credentials: true
Content-Type: text/html;charset=EUC-KR
Content-Length: 424
Date: Wed, 13 Jul 2022 06:11:40 GMT
...
Cookie: JSESSIONID=EE1C98820B6693EF2E710B74697B8074 // request header

 

์ฐธ๊ณ 


naver.com ์„ ์ž…๋ ฅ ํ›„ ๊ฐœ๋ฐœ์ž๋„๊ตฌ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•˜๋ฉด,

์—ฌ๋Ÿฌ resource๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด origin์ด ๋‹ค๋ฅธ ์š”์ฒญ์˜ response header์—๋Š” access-control-allow-origin ์˜ต์…˜์ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹ค์—…๋ฌด์—์„œ ๋งŽ์ด ๊ฒช์„ ์ˆ˜ ๋ฐ–์— ์—†๋Š” ๋ณด์•ˆ์ •์ฑ…์ด๋ฏ€๋กœ, ์›น ๊ฐœ๋ฐœ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด CORS ์ •์ฑ…์„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐ๋œ๋‹ค.

 

์ •๋ฆฌ


์งˆ๋ฌธ : CORS๋ž€ ๋ฌด์—‡์ด๋ฉฐ ์ด๊ฒƒ์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•ด๋ณด์„ธ์š”.

CORS๋Š” ์›น๊ฐœ๋ฐœ์„ ํ•˜๋‹ค๊ฐ€ ํ”ํžˆ ๋งŒ๋‚  ์ˆ˜ ์žˆ๋Š” ์ด์Šˆ์ž…๋‹ˆ๋‹ค. ๋Œ€๊ฐœ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์‹œ์— ๋กœ์ปฌ์—์„œ API ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ํ”ํ•˜๊ฒŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์„œ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ๊ฐ„์— ์ž์›์„ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ด๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜๋ฉฐ, ์„œ๋ฒ„์ธก์—์„œ ํ—ค๋”๋ฅผ ํ†ตํ•ด์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์ž์›์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

preflight request๋Š” ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๋„ ์•ˆ์ „ํ•œ์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์ „์— ๋ณด๋‚ด๋Š” ์š”์ฒญ์ž…๋‹ˆ๋‹ค. OPTIONS ๋ฉ”์„œ๋“œ๋กœ ์š”์ฒญํ•˜๋ฉฐ CORS๋ฅผ ํ—ˆ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. CORS๊ฐ€ ํ—ˆ์šฉ๋œ ์›น์„œ๋ฒ„๋ผ๋ฉด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ํ—ค๋”์— ๋‹ด์•„ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.