๐Ÿ“˜ Web/Web

[Web] CORS์˜ '๋™์ผ ์ถœ์ฒ˜ ์›์น™ ์ •์ฑ… ์œ„๋ฐ˜(Same-orgin policy)' ์—๋Ÿฌ ๋Œ€์‘

a n u e 2023. 2. 6. 11:28

์ •๋ง ์˜ค๋žœ๋งŒ์— ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋ฅผ ์ผฐ๋Š”๋ฐ ํ™”๋ฉด๋‹จ๊ณผ ์„œ๋ฒ„๊ฐ€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜๋‹ค.

๋‚ด ํ๋ฆฐ ๊ธฐ์–ต ์† ๋ถ„๋ช… ์ •์ƒ์ ์œผ๋กœ ๋Œ์•˜๋Š”๋ฐ (...)

Access to XMLHttpRequest at 'http://localhost:8081/api/diary/selectAll' 
from origin 'http://localhost:8080' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

์—ด์‹ฌํžˆ ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณด๋‹ˆ SOP ์ •์ฑ…์„ ์œ„๋ฐ˜ํ•˜์—ฌ ๊ทธ๋ ‡๋‹ค๊ณ  ํ•œ๋‹ค. 

SOP ์ •์ฑ…์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ ใ…ก.ใ…ก


โ–  SOP (Same-origin policy)

SOP๋Š” '๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…'์˜ ์•ฝ์ž๋กœ, ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค.

๋™์ผ ์ถœ์ฒ˜(origin)๋ž€, ๋‘ ๊ฐœ์˜ URL์ด ์žˆ์„ ๋•Œ, ํ”„๋กœํ† ์ฝœ + ํ˜ธ์ŠคํŠธ + ํฌํŠธ๊ฐ€ ๋™์ผํ•œ ๊ฒฝ์šฐ๋ฅผ ๋งํ•œ๋‹ค

์ฆ‰, ๋™์ผ ์ถœ์ฒ˜์ธ ๋…€์„๋“ค๋ผ๋ฆฌ๋งŒ ์—ฐ๊ฒฐ์„ ์‹œํ‚จ๋‹ค๋Š” ์ •์ฑ…์ด๋ฏ€๋กœ API๋ฅผ ํ˜ธ์ถœ ์‹œ, ์ด๋ฅผ ์œ„๋ฐ˜ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ „์†ก์„ ๋ง‰๋Š”๋‹ค.

ํ•ด๋‹น ์ •์ฑ…์€ (๋ฐ์ดํ„ฐ ์†ก์ˆ˜์‹  ์‹œ, ํ•ด์ปค๊ฐ€ ๊ฐœ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ์ง€๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ) ๋ณด์™„์ƒ์˜ ์ด์œ ๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค.

 

โš ๏ธ 
1. https://www.domain.com:5000 ์—์„œ  'https://'๊ฐ€ ํ”„๋กœํ† ์ฝœ,  'www.domain.com'์ด ํ˜ธ์ŠคํŠธ, '5000'์ด ํฌํŠธ์ด๋‹ค.
2. ๋ง‰๋Š” ๊ฒƒ์€ ์„œ๋ฒ„๊ฐ€ ์•„๋‹Œ ๋ธŒ๋ผ์šฐ์ €๊ณ , same-policy๋„ ๋ธŒ๋ผ์šฐ์ €์˜ ๋ณด์•ˆ ์ •์ฑ…์ด๋‹ค.
3  ๋ช‡ ๊ฐ€์ง€ ์ƒํ™ฉ์—์„œ๋Š” SOP์„ ์ ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.   
    HTML ๋‚ด๋ถ€ ํ˜ธ์ถœ์€ SOP ์ •์ฑ…์„ ๋ฌด์‹œํ•œ๋‹ค. XHR (js์—์„œ์˜ ์š”์ฒญ)๋งŒ SOP์ด ๊ฑธ๋ฆฐ๋‹ค.   
    <script src...> , <link...> ๋“ฑ์˜ ํƒœ๊ทธ์—์„œ ํƒ€ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€๋„ ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ๋˜ ๊ฒƒ์ด ์ด ๋•Œ๋ฌธ์ด๋‹ค.

์—ฅ ๊ทผ๋ฐ, API ์„œ๋ฒ„๋Š” ๋‹น์—ฐํžˆ ๋™์ผ ํ˜ธ์ŠคํŠธ๊ฐ€ ์•„๋‹ ํ™•๋ฅ ์ด ๋†’์€๋ฐ ์–ด์ฉŒ๋ž€ ๋ง์ด๋ƒ

์ด๋Ÿด ๋•Œ ์“ฐ๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ CORS ํ”Œ๋Ÿฌ๊ทธ์ธ์ด๋‹ค. 

๊ทธ๋Ÿผ CORS๋ž€ ๋ฌด์—‡์ผ๊นŒ


โ–  CORS (Cross-Origin Resource Sharing)

CORS๋Š” '๊ต์ฐจ ์ถœ์ฒ˜ ์ž์› ๊ณต์œ '์˜ ์•ฝ์ž์ด๋‹ค. ๋™์ผ ์ถœ์ฒ˜๊ฐ€ ์•„๋‹Œ Cross Origin์—์„œ ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋„ ํ—ˆ์šฉํ•ด ์ค€๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

CORS์˜ ๊ธฐ๋ณธ์  ์›๋ฆฌ๋Š” ์ด๋ ‡๋‹ค.

โœ…
 ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‹ค๋ฅธ origin์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒ„ โ–ถ  ๋ณด๋‚ผ ๋•Œ HTTP ์š”์ฒญ 'Origin' ํ—ค๋”์— ์ž์‹ ์˜ origin ์„ค์ • โ–ถ  ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต๋ฐ›์„ ๋•Œ, 'Access-Control-Allow-Origin' ํ—ค๋”์— ์ž์‹ ์˜ origin์ด ์žˆ๋Š”์ง€ ํ™•์ธ

* ์ด๋•Œ, Origin ํ—ค๋”์— ์™€์ผ๋“œ์นด๋“œ(*)๋ฅผ ์“ฐ๋ฉด ๋ชจ๋“  origin์„ ํ—ˆ์šฉํ•œ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋œ๋‹ค.

์‘? ๊ทผ๋ฐ HTTP ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, Origin์ด๋ผ๋Š” ํ—ค๋”๊ฐ€ ์žˆ์—ˆ๋˜๊ฐ€

์‚ฌ์‹ค ๋ธŒ๋ผ์šฐ์ €๋Š” ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „, '์˜ˆ๋น„ ์š”์ฒญ'์ด๋ผ๋Š” ๊ฒƒ์„ ๋ณด๋‚ธ ๋’ค '๋ณธ ์š”์ฒญ'์„ ๋ณด๋‚ธ๋‹ค. (100%๋Š” ์•„๋‹ˆ๊ณ , ๋Œ€๋ถ€๋ถ„)

๊ทธ๊ฒƒ์„ Preflight๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ, ์ด ๋•Œ HTTP ๋ฉ”์†Œ๋“œ๊ฐ€ GET/POST๊ฐ€ ์•„๋‹Œ OPTION์ด๋ผ๋Š” ์š”์ฒญ์„ ์‚ฌ์šฉํ•œ๋‹ค.

๋ณธ ์š”์ฒญ์„ xhr ๋ผ๊ณ  ์นญํ•˜๊ณ , ์˜ˆ๋น„ ์š”์ฒญ์„ preflight๋ผ๊ณ  ์นญํ•œ๋‹ค.

โš ๏ธ
 1. ์™ธ์— '๋‹จ์ˆœ ์š”์ฒญ'์ด๋ผ๋Š” ๊ฒƒ๋„ ์žˆ๋Š”๋ฐ '์˜ˆ๋น„ ์š”์ฒญ'์„ ์ƒ๋žตํ•˜๊ณ  ๋ฐ”๋กœ '๋ณธ ์š”์ฒญ'์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.
 ๋ช‡ ๊ฐ€์ง€ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
 2. '์ธ์ฆ๋œ ์š”์ฒญ'์ด๋ผ๋Š” ๊ฒƒ๋„ ์žˆ๋‹ค.
 ์ด๋Š” ์ž๊ฒฉ ์ธ์ฆ ์ •๋ณด๋ฅผ ์‹ค์–ด ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ๋กœ, ์ฟ ํ‚ค๋‚˜ ํ† ํฐ ๊ฐ’์„ ๋ณด๋‚ผ ๋•Œ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์•ฝ๊ฐ„ ๋ฐฉ์‹์ด ๋‹ค๋ฅด๋‹ค.

* ๋Œ€๋ถ€๋ถ„์˜ API ์š”์ฒญ์ด Preflight์ด๋ฏ€๋กœ, ์—ฌ๊ธฐ์„œ๋Š” ์ƒ๋žต์ƒ๋žต

์˜ˆ ๊ทธ๋ž˜์„œ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•˜๋Š”๋ฐ์š”

 

โ–  ํด๋ผ์ด์–ธํŠธ๋‹จ์—์„œ ํ•ด๊ฒฐ

 

 1. ๋ธŒ๋ผ์šฐ์ € ํ”Œ๋Ÿฌ๊ทธ์ธ ์‚ฌ์šฉ

ํฌ๋กฌ์˜ ๊ฒฝ์šฐ 'Allow CORS: Access-Control-Allow-Origin'์ด๋ผ ํ•˜์—ฌ ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ์ฃผ๋Š” ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์ด ์žˆ๋‹ค.

๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ž ๊น ํ…Œ์ŠคํŠธํ•  ๋•Œ ํŽธํ•  ๋“ฏ

 

2. http-proxy-middleware ์‚ฌ์šฉ

 ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋กœ์ปฌ์ธ ๊ฒฝ์šฐ์— ํ•œ์ •ํ•˜์—ฌ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ ๋‹จ์—์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 2. ํ”„๋ก์‹œ ์‚ฌ์šฉํ•˜๊ธฐ

ํ”„๋ก์‹œ(proxy)๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์‚ฌ์ด์˜ ๋Œ€๋ฆฌ์ž ์—ญํ• ์„ ํ•ด์ฃผ๋Š” ์ค‘๊ณ„ ์„œ๋ฒ„์ด๋‹ค.

๋ชจ๋“  ์ถœ์ฒ˜๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๊ฐ€์šด๋ฐ์— ๋ผ์šฐ๋ฉด ๋œ๋‹ค. SOP์„ ์šฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•.

ํ‘œ๋ฉด ์ƒ์œผ๋กœ๋Š”, ํด๋ผ์ด์–ธํŠธ์˜ IP๋กœ ์š”์ฒญ์ด ๋‚ ์•„๊ฐ€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ํ”„๋ก์‹œ๋ฅผ ๊ฑฐ์นœ๋‹ค.

๋‹ค๋งŒ, ๋ฌด๋ฃŒ ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” API ์š”์ฒญ ํšŸ์ˆ˜์— ์ œํ•œ์ด ์žˆ์œผ๋ฏ€๋กœ ์ด๊ฒƒ ๋˜ํ•œ ํ…Œ์ŠคํŠธ ์šฉ๋„๋กœ ๋ช‡ ๋ฒˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์•„๋‹ˆ๋ฉด ์ง์ ‘ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

์•„๋ž˜ ์˜ˆ์‹œ๋Š” ํ”„๋ก์Šค ์„œ๋ฒ„ ์ค‘ ํ•˜๋‚˜์ธ cors proxy app ํ”„๋ก์‹œ ์„œ๋ฒ„์ด๋‹ค.

<script src='https://cdnjs.cloudflare.com/ajax/libs/axios/1.1.3/axios.min.js'></script>
<script>
    axios({
        url: 'https://cors-proxy.org/api/',
        method: 'get',
        headers: {
            'cors-proxy-url' : 'https://localhost:8000/' //์‚ฌ์šฉํ•  URL๋กœ ๋ณ€๊ฒฝ
        },
    }).then((res) => {
        console.log(res.data);
    })
</script>

โ–  ์„œ๋ฒ„๋‹จ์—์„œ ํ•ด๊ฒฐ

 

์ •์„์ ์ธ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์€ ์„œ๋ฒ„์—์„œ ์…‹ํŒ…ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.  ๋ฐฉ๋ฒ•์€ ์ด 3๊ฐ€์ง€์ด๋‹ค.

 

 1. Controller์— @CrossOrigin ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉํ•˜๊ธฐ

@RequestMapping("api/test")
@RestController
@RequiredArgsConstructor
@CrossOrigin
public class TestController {
   ...
}

 

2. WebConfig ํŒŒ์ผ ์ƒ์„ฑ

 (1) ์ฒซ๋ฒˆ์งธ ๋ฐฉ๋ฒ•

  WebMvcConfigurer์„ ์ƒ์†๋ฐ›๋Š” WebConfig ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์…‹ํŒ…ํ•ด์ค€๋‹ค. 

  ํด๋ž˜์Šค๋ฅผ ์ด๋ ‡๊ฒŒ ๋”ฐ๋กœ ๋งŒ๋“ค๊ฑฐ๋ฉด, @Configuration ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ค€๋‹ค.

  allowedOrigins์— ์‘๋‹ต์„ ์ „ํ•ด์ค„ origin์„ ์ ์œผ๋ฉด ๋œ๋‹ค.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080/");
    }
}

 

 (2) ๋‘๋ฒˆ์งธ ๋ฐฉ๋ฒ•

  @SpringBootApplication ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ main ํŒŒ์ผ์— @Bean ์œผ๋กœ ๋“ฑ๋กํ•ด์ฃผ์–ด๋„ ๋œ๋‹ค.

package com.eaproj.one;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }


    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("http://localhost:8080/");
            }
        };
    }
}

 

 3. CorsFilter๋ฅผ ๋งŒ๋“ค์–ด Access-Control-Allow-Origin ํ—ค๋” ์„ธํŒ…

 Filter๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค. 

response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");

์ด ๋ถ€๋ถ„์—์„œ origin์„ * ๋กœ ๋ฐ”๊พธ๋ฉด

ํŠน์ • origin์ด ์•„๋‹Œ, ๋ชจ๋“  origin์—๊ฒŒ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ๋‹น์—ฐํžˆ ๋ณด์•ˆ์ƒ ์ทจ์•ฝํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

package com.eaproj.one.config;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods","*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept, Authorization");

        if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        }else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void destroy() {

    }
}

 

 

โ–ถ  ๊ธ€์„ ์ž‘์„ฑํ•˜๋Š” ๋„์ค‘, CORS์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ์•Œ์•˜๋‹ค. ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์—๋Š” @CorssOrigin์ด ์ž˜ ๋ถ™์–ด์žˆ์—ˆ๋Š”๋ฐ, ์ปจํŠธ๋กค๋Ÿฌ ์ž์ฒด๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋‹ˆ SOP ์ •์ฑ… ์œ„๋ฐ˜์ด๋ผ๋Š” ์—๋Ÿฌ๊ฐ€ ๋œฌ ๊ฒƒ

 

  ํŒจํ‚ค์ง€๋ฅผ ์˜ฎ๊ธฐ๋ฉด์„œ ํŒŒ์ผ์„ ์ •๋ฆฌํ•œ ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ ๊ทธ๋•Œ ์ปจํŠธ๋กค๋Ÿฌ ๊ด€๋ จ ์–ด๋…ธํ…Œ์ด์…˜์ด ๋น ์ง„ ๋“ฏ ํ•จ

  ๊ทธ๋ž˜ ์›๋ž˜ ๋์—ˆ๋‹ค๋‹ˆ๊นŒ .. TT ๊ทธ๋ž˜๋„ ๋” ์ž์„ธํ•˜๊ฒŒ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋˜์–ด์„œ ์ข‹์•˜๋‹ค.

 

๋‚œ Controller ๋งˆ๋‹ค ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ธ ๊ฒƒ ๊ฐ™์•„์„œ CorsFilter๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

 3๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋‹ค ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•˜๊ณ  ๋ชจ๋‘ ์ •์ƒ ๋™์ž‘ํ•œ๋‹ค. 

 

๋-!!