💻 개발/DB&서버&네트워크&암호

[CORS] 스프링부트 CORS / 리액트 Proxy가 우리를 힘들게 한다... FEAT. 스프링시큐리티 CORS

foodev 2023. 3. 26. 15:09
728x90

스프링시큐리티와, webconifg에 두가지 설정법을 다룬다.

 

 

 

 

토이 프로젝트를 진행 중에 CORS 문제를 만나게 되어 글을 작성하게 되었다.

우리 팀은리액트(3000 포트)와 스프링부트(8080 포트)를 이용해 개발 중에  있다.

 

토요일 주말, 쉬고 있던 중 한 팀원으로 부터 다음과 같은 연락을 받았다. 

"localhost:3000/api/find/team API에서 응답값이  나와요!"

팀원의 문자 내용 중.../find/team 아니고 /team/find...

 

 

응???

나는 순간적으로 localhost:3000은 리액트 포트인데 무슨 소리지? 하면서 당황했다.

뭔가 착각한거 아니야?라는 마음으로 포스트맨에서 

토큰 값과 localhost:3000/api/team/find에 파라미터 값을 넣고 실행해 보니

정말 응답 값이 나왔다.

 

자 그러면 localhost:3000으로 API 요청을 했는데 정상응답값이 나오는 상황과 이유는 뭔지 알아보자.

 

 

문제상황


주어진 상황을 고려해 보면, 프런트엔드에서 백엔드로 요청하는 것이 아닌, 프론트엔드에서 자기 자신으로 요청하는 상황이다.

이렇게 되면 백엔드에서 응답을 받을 수 없으므로, 정상적인 동작이 아니며 예기치 않은 결과가 발생해야 한다.

하지만 프런트엔드에서 프런트엔드로(자기 자신 port로) 포스트맨으로 응답 값 테스트 시 정상 응답 값이 나왔다. 

 

해당 원인은 무엇일까? 

 

 

해당원인


브라우저에서 실행

API가 실행되는 것이 localhost:3000 (프런트)에서 -> localhost:3000 (프런트)로 요청해도 나오는 것은,

프런트엔드에서 해당 API의 URL을 호출하면, 해당 API URL의 코드가 브라우저에서 실행되기 때문이다.

 

이 경우는 CORS(Cross-Origin Resource Sharing) 정책이 적용되지 않아

프론트엔드에서 자신의 도메인으로 요청을 보내도 서버에서 처리가 가능하다고 한다. 

 

이를 Same-Origin Policy라고 부르며, 프런트엔드와 백엔드가 같은 도메인이에서 실행되는 경우에만 적용된다. 

 

도메인 / 포트 차이 

도메인은 localhost로 같고 포트는 3000, 8080으로 다른 상태이다. 

즉 이를 Same-Origin Policy

Same-Origin Policy

Same-Origin Policy는 웹 보안에서 가장 중요한 보안 정책 중 하나로, 웹 브라우저가 다른 출처(origin)로부터 가져온 리소스에 접근하는 것을 제한하는 보안 메커니즘이다.

 

* 출처

URL 스키마, 호스트 이름,. 포트 번호로 구성된 URL 일부를 의미한다. 

만약 브라우저가 현재 열린 웹 페이지의 출처와 다른 출처에서 리소스를 가져온다면

Same-Origin Policy에 따라 해당 리소스에 접근할 수 없다.

 

이 보안 정책은 다른 출처에서(다른 외부 페이지) 스크립트를 사용하여 사용자 정보를 탈취하거나, CSRF와 같은 공격을 방지하기 위해 필요하다. (CSRF는 다루지 않음) 브라우저는 Same-Origin Policy를 적용하여, 웹 페이지가 리소스를 가져오는 동안 이러한 보안 위협으로부터 사용자를 보호한다. 

 

예를 들어 http://example.co.kr이라는 출처에서 로그인 정보를 입력하고 이 정보를 백엔드 서버로 전송하는데,

이때 로그인 정보를 받아오는 페이지는 같은 출처 http://example.co.kr/login에서만 가져올 수 있다.

만약 다른 출처 http://other.co.kr에서 http://example.co.kr/login에 대한 axios나 ajax를 사용한다면

Same-Origin Policy에 의해  로그인 정보를 가져올 수 없으므로, 이를 이용한 정보 해킹이나, 공격으로부터 방지할 수 있다. 

 

즉 CORS 정책으로 인해 발생하는 문제라고 볼 수 있다.

 

문제해결


CORS 문제는 프론트엔드와 백엔드 두 곳에서 해결할 수 있는데

여기서는 스프링부트에서 처리하는 방법을 다룬다.

 

스프링부트 처리 방법

스프링부트에서 처리하는 방법으로는 

Controller마다 CORS 설정을 주어 처리하는 방법과 

WebConfig를 이용해서 전역적으로 해결하는 방법이 있다.

 

WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://example.com",
                        "https://www.example.com",
                        "http://localhost:3000/",
                        "https://example.app/")
                .allowedMethods("*");
    }

 

1. Config 폴더에 WebConfig파일을 만들어 주고, WebMvcConfigurer 인터페이스를 선언

2. .addMapping은 어떤 경로의 요청인지 정의

3. .allowedOrigins는 어떤 도메인으로 부터의 요청인지 정의

    - 보통 프론트엔드에서 로컬, 개발 서버에서 요청을 하므로, http://localhost:3000과 개발서버를 선언한다.

4. .allowedMethod는 DELETE, POST, GET, PUT 메소드 중 어떤 메소드를 허용할지 정의를 한다

 

위의 방법으로 해줬는데 CORS 문제가 지속적을 발생했다. 

따라서 아래와 같이 allowCredentials(true)를 추가해줬더니 해결됐다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

 

 

Controller마다 선언하는 방법은 내가 생각하기에 비효율적이라고 생각하여 WebConfig만 다뤘다. 

 

 

스프링시큐리티 CORS 적용

추가적으로 스프링 시큐리티를 이용하고 있다면

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .antMatchers("/api/auth/**").permitAll()
                                .antMatchers(PERMIT_URL_ARRAY).permitAll()
                                .anyRequest().authenticated()
                );

        http.addFilterBefore(new JwtAuthFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

 

다음과 같이 cors()를 추가해줘서 시큐리티의 필터 앞단에서 CROS 적용을 허용 해줘야한다.

http.cors().and()

나의 경우는 스프링시큐리티를 적용해서 CORS 문제를 해결할 수 있었다.

 

728x90