CORS

Posted by : on

Category : websecurity


CORS๋ž€?


CORS(Cross-Origin Resource Sharing)๋Š” ์ถœ์ฒ˜๊ฐ€ ๋‹ค๋ฅธ ์ž์›๋“ค์„ ๊ณต์œ ํ•œ๋‹ค๋Š” ๋œป์œผ๋กœ, ํ•œ ์ถœ์ฒ˜์— ์žˆ๋Š” ์ž์›์—์„œ ๋‹ค๋ฅธ ์ถœ์ฒ˜์— ์žˆ๋Š” ์ž์›์— ์ ‘๊ทผํ•˜๋„๋ก ํ•˜๋Š” ๊ฐœ๋…์ด๋‹ค. ์ง์—ญํ•˜๋ฉด, ๊ต์ฐจ๋˜๋Š” ์ถœ์ฒ˜ ์ž์›๋“ค์˜ ๊ณต์œ ! ๋‹ค๋ฅธ ์ถœ์ฒ˜์— ์žˆ๋Š” ์ž์›์„ ์š”์ฒญํ•œ๋‹ค๊ณ  ํ•˜๋ฉด, ์ด๋ฅผ ๊ต์ฐจ ์ถœ์ฒ˜ ์š”์ฒญ์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

์ถœ์ฒ˜๋ž€?

Pasted image 20240202165833.png

์œ„์˜ ๊ตฌ์„ฑ์š”์†Œ ์ค‘์—์„œ Protocol + Host + Port 3๊ฐ€์ง€๊ฐ€ ๊ฐ™์œผ๋ฉด ๋™์ผ ์ถœ์ฒ˜(Origin)๋ผ๊ณ  ํ•œ๋‹ค.

๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…(Same-origin policy)


๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…(Same-origin policy)๋Š” ๋‹ค๋ฅธ ์ถœ์ฒ˜๋กœ๋ถ€ํ„ฐ ์กฐํšŒ ๋œ ์ž์›๋“ค์˜ ์ฝ๊ธฐ ์ ‘๊ทผ์„ ๋ง‰์•„ ๋‹ค๋ฅธ ์ถœ์ฒ˜ ๊ณต๊ฒฉ์„ ์˜ˆ๋ฐฉํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๋‹ค๋ฅธ ์ถœ์ฒ˜์—์„œ ์–ป์€ ์ด๋ฏธ์ง€๋ฅผ ๋‹ด๋Š” img, ์™ธ๋ถ€ ์ฃผ์†Œ๋ฅผ ๋‹ด๋Š” link ๊ฐ™์€ ์—ฌ๋Ÿฌ ํƒœ๊ทธ๋“ค์„ ํ—ˆ์šฉํ•œ๋‹ค. ๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…์˜ ์ •ํ™•ํ•œ ๊ตฌํ˜„ ๋ช…์„ธ๋Š” ์—†์ง€๋งŒ ์ตœ์‹ ์˜ ๋ธŒ๋ผ์šฐ์ €๋“ค์€ ์ผ์ • ๊ทœ์น™์„ ๋”ฐ๋ฅด๊ณ  ์žˆ๋‹ค. Pasted image 20240202170012.png

์š”์ฒญํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์š”์ฒญ๋ฐ›๋Š” ์„œ๋ฒ„๊ฐ€ ๊ฐ™์€ ์ถœ์ฒ˜์— ์žˆ์œผ๋ฉด ๋™์ผ ์ถœ์ฒ˜, ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋ฒ„์— ์žˆ์œผ๋ฉด ๋‹ค๋ฅธ ์ถœ์ฒ˜ ์š”์ฒญ

Pasted image 20240202170045.png

์œ„์— ํƒœ๊ทธ๋“ค ์ œ์™ธํ•˜๊ณ  ์ด๊ฑธ ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ๋“ค์ด ๋ช‡ ๊ฐœ ์žˆ๋‹ค.

1. ๋‹จ์ˆœ์š”์ฒญ(Simple Request)


  • GET, HEAD, POST ์š”์ฒญ๋งŒ ๊ฐ€๋Šฅ๋‹ค

  • Accept, Accept-Language, Contet-Language, Content-Type๊ณผ ๊ฐ™์€ CORS ์•ˆ์ „ ๋ฆฌ์ŠคํŠธ ํ—ค๋” ํ˜น์€ User-Agent ํ—ค๋”

  • Contet-Type ํ—ค๋”๋Š” application/x-www-form-urlencoded, multipart/form-data and text/plain๋งŒ ๊ฐ€๋Šฅ

    https://developer.mozilla.org/ko/docs/Web/API/ReadableStream

  • XMLHttpRequest ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญํ•˜๋ฉด, ์š”์ฒญ์—์„œ ์‚ฌ์šฉ๋œ XMLHttpRequest.upload์— ์˜ํ•ด ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด์— ์–ด๋– ํ•œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋„ ๋“ฑ๋ก๋˜์ง€ ์•Š๋Š”๋‹ค.

์ฝ”๋“œ๋กœ ํ™•์ธ.

const xhr = new XMLHttpRequest(); 
const url = 'https://www.api.com?q=test'; 
xhr.open('GET', url); 
xhr.onreadystatechange = requestHandler; 
xhr.send();

Pasted image 20240202170146.png

๋งŒ์•ฝ ์„œ๋ฒ„๊ฐ€ ์ด ํ—ค๋”์— ์‘๋‹ตํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ํ—ค๋” ๊ฐ’์ด ์š”์ฒญ์˜ ์ถœ์ฒ˜์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋„๋ฉ”์ธ์ธ ๊ฒฝ์šฐ, ๋ธŒ๋ผ์šฐ์ €๋Š” ์‘๋‹ต์„ ์ฐจ๋‹จํ•œ๋‹ค. ๋˜ํ•œ ์š”์ฒญํ•œ ์ถœ์ฒ˜๊ฐ€ ์„œ๋ฒ„์˜ access-conrol-allow-origin์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋‹ค.

2. ํ”„๋ฆฌ ํ”Œ๋ผ์ดํŠธ(Preflight Request) ์ด๊ฒŒ ์ œ์ผ ์ข‹์€๋“ฏ


ํ”„๋ฆฌ ํ”Œ๋ผ์ดํŠธ๋Š” OPTIONS ๋ฉ”์„œ๋“œ๋กœ HTTP ์š”์ฒญ์„ ๋ฏธ๋ฆฌ ๋ณด๋‚ด ์‹ค์ œ ์š”์ฒญ์ด ์ „์†กํ•˜๊ธฐ์— ์•ˆ์ „ํ•œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ถœ์ฒ˜ ์š”์ฒญ์ด ์œ ์ € ๋ฐ์ดํ„ฐ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฏธ๋ฆฌ ์ „์†กํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

์š”์ฒญ ํ—ค๋”์—๋Š” ๋‹ค์Œ ๊ฐ’์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

origin : ์–ด๋””์„œ ์š”์ฒญ์„ ํ–ˆ๋Š”์ง€ ์„œ๋ฒ„์— ์•Œ๋ ค์ฃผ๋Š” ์ฃผ์†Œ

access-control-request-method : ์‹ค์ œ ์š”์ฒญ์ด ๋ณด๋‚ผ HTTP ๋ฉ”์„œ๋“œ

access-control-request-headers : ์‹ค์ œ ์š”์ฒญ์— ํฌํ•จ๋œ header

์‘๋‹ต ํ—ค๋”์—๋Š” ๋‹ค์Œ ๊ฐ’์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

access-control-allow-origin : ์„œ๋ฒ„๊ฐ€ ํ—ˆ์šฉํ•˜๋Š” ์ถœ์ฒ˜
access-control-allow-methods : ์„œ๋ฒ„๊ฐ€ ํ—ˆ์šฉํ•˜๋Š” HTTP ๋ฉ”์„œ๋“œ ๋ฆฌ์ŠคํŠธ
access-control-allow-headers : ์„œ๋ฒ„๊ฐ€ ํ—ˆ์šฉํ•˜๋Š” header ๋ฆฌ์ŠคํŠธ
access-control-max-age : ํ”„๋ฆฌ ํ”Œ๋ผ์ดํŠธ ์š”์ฒญ์˜ ์‘๋‹ต์„ ์บ์‹œ์— ์ €์žฅํ•˜๋Š” ์‹œ๊ฐ„

const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open(โ€˜GET', url);
xhr.setRequestHeader(โ€˜custom-header', โ€™test')
xhr.onreadystatechange = requestHandler;
xhr.send();

3. ์‹ ์šฉ ์š”์ฒญ(Credentialed Request)


์‹ ์šฉ ์š”์ฒญ์€ ์ฟ ํ‚ค, ์ธ์ฆ ํ—ค๋”, TLS ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ ๋“ฑ์˜ ์‹ ์šฉ์ •๋ณด์™€ ํ•จ๊ป˜ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ, CORS ์ •์ฑ…์€ ๋‹ค๋ฅธ ์ถœ์ฒ˜ ์š”์ฒญ์— ์ธ์ฆ์ •๋ณด ํฌํ•จ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์š”์ฒญ์— ์ธ์ฆ์„ ํฌํ•จํ•˜๋Š” ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ๊ฑฐ๋‚˜ access-control-allow-credentials๊ฐ€ true๋กœ ์„ค์ • ํ•œ๋‹ค๋ฉด ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธ์ฆ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒ ์Šต๋‹ˆ๋‹ค.

const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();

Pasted image 20240202170258.png

๋งŒ์•ฝ ์„œ๋ฒ„ ์‘๋‹ต์— access-control-allow-credentials ๊ฐ€ true๋กœ ์„ค์ •๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ access-control-allow-origin ํ—ค๋”์— ์žˆ๋Š” ๊ฐ’์ด ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.


Spring Boot์—์„œ cross-origin ์„ค์ •ํ•˜๊ธฐ


spring Boot์—์„œ cross-origin์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฉ”์„œ๋“œ ์„ค์ •, ์ปจํŠธ๋กค๋Ÿฌ ์„ค์ •์ด ์žˆ์œผ๋ฉฐ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ ์šฉ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”์„œ๋“œ์— ์„ค์ •ํ•˜๊ธฐ


@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

retrieve() ๋ฉ”์„œ๋“œ์— ์„ ์–ธ๋œ @CrossOrigin์˜ ๊ธฐ๋ณธ ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๋ชจ๋“  ์ถœ์ฒ˜๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

  2. ํ—ˆ์šฉ๋œ HTTP ๋ฉ”์„œ๋“œ๋Š” @RequestMapping์— ์„ ์–ธ๋œ ๋ฉ”์„œ๋“œ๋“ค์ž…๋‹ˆ๋‹ค.

  3. ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ ์‘๋‹ต์€ 30๋ถ„ ๋™์•ˆ ์บ์‹œ๋ฉ๋‹ˆ๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ์— ์„ค์ •ํ•˜๊ธฐ


@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

์ปจํŠธ๋กค๋Ÿฌ์— ์„ค์ •ํ–ˆ์œผ๋ฏ€๋กœ AccountController์— ์žˆ๋Š” retrieve() ์™€ remove() ํ•จ์ˆ˜ ๋ชจ๋‘์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ณ„ ์ ์šฉ์‹œํ‚ค๊ธฐ


@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("http://example.com")
    @RequestMapping(method = RequestMethod.GET, "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

์Šคํ”„๋ง์—์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ CORS ์ •์ฑ…์„ ๋ณตํ•ฉํ•ด์„œ ์„ค์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ๋ฉ”์„œ์„œ๋“œ๋“ค์€ 3600์ดˆ๊ฐ€ ์บ์‹œ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค. retrieve() ๋ฉ”์„œ๋“œ๋Š” ํ—ˆ์šฉ ์ถœ์ฒ˜๊ฐ€ โ€œhttp://example.comโ€ ๋ฐ–์— ์•ˆ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, remove() ๋ฉ”์„œ๋“œ๋Š” ๋ณ„๋„์˜ ์„ค์ •์ด ์—†์œผ๋ฏ€๋กœ ๋ชจ๋“  ์ถœ์ฒ˜๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ „์—ญ ์„ค์ •ํ•˜๊ธฐ


CORS ์ •์ฑ…์˜ ์„ค์ •์€ WebMvcConfigurer๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ•„ํ„ฐ ๊ธฐ๋ฐ˜์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ „์—ญ์ ์œผ๋กœ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

About ์œ ์žฌ์„
์œ ์žฌ์„

๊ฐœ๋ฐœ์ž ์œ ์žฌ์„ ์ž…๋‹ˆ๋‹ค. Web Developer.

Email : jaeseok9405@gmail.com

Website : https://github.com/yoo94