CS/Network

● 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)

prden 2022. 10. 11. 14:41

1.SOP(Same Origin Policy)

1)동일 출처 정책(same-origin policy)은

어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식이다. 동일 출처 정책은 동일 출처에서 오지 않은(다른 출처에서 온) 잠재적으로 해로울 수 있는 요청을 분리함으로써 공격받을 수 있는 경로를 차단한다. (예를 들어 특정 사이트에 내가 로그인하면서 토큰을 발급 받을 때 해커가 그 토큰을 가로채서 다른 요청을 서버에 보낸다면 서버 측에서 다른 출처로 인식 되기 때문에 요청을 거부하게 되는 것이다.)

ex) nginx(웹서버)로 Vue(프론트) 와 내장 tomcat(WAS) Springboot(Api Server) 간 통신의 경우 생각

 

출처란? : 프로토콜, host, port를 통해 같은 출처인지 다른 출처인지 판단할 수 있다. 

아래 예시를 보자

src = https://www.youtube.com/watch?v=-2TgkKYmJt4

 

src = https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy

2) SOP의 역할 :

CrossOrigin에 해당하면 출처를 받아들이지 않게 함 

 

2. CORS란? :

다른 출처의 자원을 공유

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 

 

1) CORS 접근제어 시나리오 

a. 단순 요청(Simple Requet) : preflight 요청없이 바로 요청을 날린다. 

 다만, 다음의 조건을 모두 만족해야 바로 simple request보낼 수 있다. 

1. GET, POST, HEAD메서드 중 하나여야 하고

2. CONTENT - Type이

a. application/x-www-form-unlencoded

b. multipart/form-data

c. text/plain

중 하나여야 하고

3. 헤더는 Accept, Accept-Language, Content-Language, Content-Type만 허용된다. 

 

b. 프리플라이트 요청(Preflight Request) :

클라이언트에서 요청하는 URL이 외부 도메인일 경우, 웹 브라우저에서 자체적으로 실행된다. 웹 브라우저에서 실제로 요청하려는 경로와 같은 URL에 대해 서버에 OPTIONS 메서드로 사전요청을 보내고 요청할 수 있는 권한인지 확인한다. 

요청이 가능할 경우 실제 요청을 보낸다.(api 요청을 2번 보냄)  

 

헤더에는 요청의 출처, 실제요청메서드, 실제요청의 추가헤더뭐뭐 보낼 수 있는지 보낸다.

src = https://www.youtube.com/watch?v=-2TgkKYmJt4

 

서버측에서 허가한 출처, 메서드, 여러 헤더를 반환해서 보내고 prefligt를 사전에 요청할 때마다 보내는 건 낭비니까 캐싱해두고 max-age넘어갈 경우 또보내고(2번 요청) 하는 형식을 취한다. 또한 Prefligt response는 응답 코드가 200대여야하고 응답바디는 비어있는 것이 좋다.

src = https://www.youtube.com/watch?v=-2TgkKYmJt4

 

simplerequest한 번만 날리고 끝내면 되는 것 아닌가??? 왜 prefligt가 필요한 이유는??

CORS를 모르는 서버를 위해서 필요한 것이다. 클라이언트가 브라우저에게 특정요청(예를 들어 delete요청)을 보내면 브라우저는 서버로 일단 요청을 보내고 server에서는 delete 요청을 수행한 뒤에 respose를 보낸다. 그러면 브라우저에서는 ALLOW-ORIGIN이 없으니 CORS에러를 터트리는데 이미 server에서는 delete요청을 수행한 상황이다. 따라서 사전에 prefligt요청을 보내게 끔해서 CrossOrgin일 경우 실제 요청을 보내지 못하도록 막는 것이다.

 

src = https://www.youtube.com/watch?v=-2TgkKYmJt4

c. 인증정보 포함 요청(Credential Request):

인증 관련 헤더를 포함할 때 사용하는 요청으로, 클라이언트 측에 credentials : include를 넣고

서버측에 Access-Control-Allow-Credentials : true로 놓으면 됨.(*로 두면 에러남)

 

2) CORS 해결

1. 프론트 프록시 서버 설정(개발 환경)

운영환경에서는 rever proxy를 적용하여 프런트를 거쳐서 백엔드로 가도록 설정하기 때문에 해당 사항이 없다. 또한 개발환경에서도 요즘은 package.json에서 proxy 를 설정하면 CORS 문제는 생기지 않는다.

 

vue.config.js에서 프록시 설정

 proxy: {
      '/api': {
        target: process.env.VUE_APP_API_URL,
        changeOrigin: true,
      },
    },

 

2. 운영환경에서의 문제 

1) Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

const instance = axios.create({
  baseURL: baseURL
  timeout: 2500,
  withCredentials: false, // 여기 false로 바꿔서 해결
  headers: {
    'Content-Type': 'application/json; charset=utf-8;',
    'api-key': '1234',
    'access-token': getCookie.accessToken,
  },
});

https://bbangaro.tistory.com/49

 

[CORS] cors error 해결 방법 (Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*')

CORS (Cross-Origin Resource Sharing) 브라우저 보안 정책의 이유로 교차 출처(도메인, 프로토콜, 포트)의 경우 리소스 교환을 안되게 막아 놓았는데 이를 가능하게 해주는 방법중 하나가 http 요청시 headers

bbangaro.tistory.com

2) Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

https://cheershennah.tistory.com/229

 

CORS란? No 'Access-Control-Allow-Origin' header is present in the requested resource. 에러해결

웹 개발시 클라이언트단의 브라우저에서 외부 서버로 ajax 요청 시 자주 발생하는 실패 메세지. Fail to load '호출URI': No 'Access-Control-Allow-Origin' header is present in the requested resource. Origin '요청URI' is ther

cheershennah.tistory.com

 

3. 스프링 부트, 스프링 시큐리티를 이용하기 

REST API를 postman 같은 툴로 테스트할 때는 CORS 문제가 생기지 않는다. 하지만 리액트, 뷰와 같은 프레임웍을 사용해서 브라우저에서 API를 호출시 만일 프런트서버와 백엔드 서버가 각각 존재한다면 cross domain 이슈가 발생한다. 개발환경에 대비해서 main 클래스가 있는곳에 CORS 필터를 등록한다

SecurityConfig

@EnableWebSecurity
// preAuthorize 어노테이션 메서드 단위로 사용하기 위함
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    private final TokenProvider tokenProvider;
    private final CorsFilter corsFilter;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;


    public WebSecurityConfig(TokenProvider tokenProvider,
                             CorsFilter corsFilter,
                             JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
                             JwtAccessDeniedHandler jwtAccessDeniedHandler) {
        this.tokenProvider = tokenProvider;
        this.corsFilter = corsFilter;
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/h2-console/**",
                "favicon.ico",
                "/error");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 토큰을 사용하는 방식이기 때문에 csrf를 disable한다.
                .csrf().disable()
                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
                // 예외처리 핸들링 할 때 우리가 만든 2가지 아래 401, 403
                .exceptionHandling()
                // jwtAuthenticationEntryPoint 401
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // jwtAccessDeniedHandler 403
                .accessDeniedHandler(jwtAccessDeniedHandler)

                // enable H2-console
                .and()
                .headers()
                .frameOptions()
                .sameOrigin()
                // 세션을 사용하지 않기 때문에 STATELESS로 설정
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                .antMatchers("/api/hello").permitAll()
                .antMatchers("/api/authenticate").permitAll()
                .antMatchers("/api/signup").permitAll()
                .anyRequest().authenticated()

                .and()
                .apply(new JwtSecurityConfig(tokenProvider));

        return httpSecurity.build();
    }


}


//CORS 필터

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {

        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        //allowCredential true일 경우 출처를 모든 것 허용하면 원래 안된다. 정확한 orgin설정해줘야
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("\"http://localhost:8080\"");
        config.addAllowedMethod("*");
        config.addExposedHeader("x-access-token");
        config.addExposedHeader("content-disposition");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return new CorsFilter(source);
    }
}

 

※ 참고자료:  https://www.youtube.com/watch?v=-2TgkKYmJt4 

https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

'CS > Network' 카테고리의 다른 글

MIME 타입  (0) 2022.12.22
Http와 REST  (0) 2022.12.10
CURL(Client URL)  (0) 2022.09.15
IP 주소 묶는 법 CIDR  (0) 2022.06.01
소켓 프로그래밍  (0) 2022.04.21