본문 바로가기
SpringBoot

[Spring Boot][Error] blocked by CORS policy

by 청양호박이 2021. 3. 21.

지난 번에 vue project와 springboot project의 결함에 대해서 알아보았습니다. Vue는 SPA (Single Page Application)을 구현해 주는 프로젝트이며, springboot와 결함함으로써 Frontend와 Backend의 일원화를 가능하게 해 줍니다. 해당 Project를 build하여 서버에 구성하기 위해서는 다음의 절차를 거치게 됩니다.

 

  1. vue project를 npm으로 build실행 - npm run build
  2. build의 결과는 project > config > index.js에 정의되어 있는 경로로 이동
  3. springboot project를 build 및 run

 

따라서 개발자는 개발하는 단계에서 vue project에 뭐라도 살짝 건드리게 된다면... 위의 절차를 계속 반복해야 합니다. 이는 생각하기에 따라서 불편함이 없을 수 있으나, 직접 겪어본다면 참으로 비효율적이지 않을 수 없습니다. 그래서 사용하는 것이 vue project를 dev상태로 구동하는 것 입니다. 그렇게 되면, vue project와 springboot project가 별도로 동작하기 때문에 Frontend의 수정이 있어도 복잡한 절차를 거치지 않아도 됩니다.

 

심지어, vue project를 npm run dev로 구동하게 되면... config등 변경을 제외하고는 바로 반영이 되어 확인이 가능합니다. 그렇다면 개별로 동작시켜 볼까요??

 

 

1. npm run dev


말이 나온김에 vue project에서 npm run dev를 실행해 봅니다.

 

[Frontend 구동]

C:\Users\VueProjects\atproject\at-vue>npm run dev

> at-vue@1.0.0 dev C:\Users\SDS\VueProjects\atproject\at-vue
> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js

You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.

코드에 몇가지 구문오류가 있지만 정상적으로 구동이 되었습니다. 그렇다면 이 상태에서 springboot application을 구동해 보겠습니다. 

 

[Backend 구동]

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-03-21 11:57:18.793 ERROR 7352 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

위와 같이, 8080포트가 이미 사용중이라고 Error가 발생하고 구동이 되지 않습니다. 이는 npm run dev로 이미 8080포트로 동작하는 서버가 구동이 됬는데, 동일한 포트로 서버생성을 시도했기 때문입니다.

 

그렇다면, vue의 설정을 확인해 보겠습니다. dev에 대한 설정은 아래의 경로 파일에 다음과 같이 세팅되어있습니다.

 

[config > index.js]

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080,

그렇다면, 둘중의 하나의 설정을 바꾸어야 겠습니다.

 

 

2. springboot server port 변경


아무래도 실제로 서비스를 하기위해서 변경되는 부분은 vue project의 설정보다는... springboot의 설정이 더 클 것 입니다. 왜냐하면, https를 적용하기 위해서도 springboot의 설정을 수정해야하고 여러가지 변수가 생길 수 있기 때문입니다. 결국 vue project에서는 동일서버에 설치될 경우 springboot의 설정을 따라가기 때문입니다.

이제는 위의 그림과 같이 구성을 바꿔보겠습니다. vue project는 그대로 두고... springboot project에서 구동하는 서버의 포트를 변경하겠습니다. 

 

[src > main > resource > applicaton.yml]

server:
  port: 8888
  servlet:
    session:
      timeout: 30

원하는 포트를 다음과 같이 application.yml에 추가해 주면 됩니다.

 

 

3. Frontend에서 api 호출


이제 양쪽 side의 서버를 구동했으니, 화면에서 api를 호출해 보도록 하겠습니다. 

 

[Error 발생]

GET http://localhost:8080/qss/listPerUser?bsnsYear=2020&bsnsQuarter=1&userId=Ayotera+Lab 
net::ERR_CONNECTION_REFUSED

axios를 통해서 Backend server로 API요청을 하였습니다. 대상 url은 /qss/listPerUser 입니다. 하지만, 현재 Backend는 8888 port로 구동하였고... axios는 8080으로 시동하여 Error가 발생하였습니다.

 

그렇다면, axios에서 요청을 할 때, default port를 8080에서 8888로 변경해줘야 합니다. 이를 위해서는 최초 vue instance 생성의 시작이라고 할 수 있는... main.js를 수정해 줍니다.

 

[src > main.js]

import Vue from 'vue';
import vuetify from '@/plugins/vuetify';
import axios from 'axios';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

// baseURL 기본값을 정의한다
axios.defaults.baseURL = 'http://localhost:8888/';

console.log(axios.defaults);

우선 axios를 import 시켜주고... axios.defaults.baseURL에 시작이되는 url을 명시해 줍니다. 이 설정이 없을경우에는 항상 http://localhost:8080이 기본으로 세팅됩니다.

 

이렇게 변경하고 실행해 보면... 역시나 또 Error가 발생합니다.

adapter: ƒ xhrAdapter(config)
baseURL: "http://localhost:8888/"


Access to XMLHttpRequest at 
'http://localhost:8888/qss/listPerUser?bsnsYear=2020&bsnsQuarter=1&userId=Ayotera+Lab' 
from origin 'http://localhost:8080' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

일단 baseURL은 정상적으로 바뀜을 console.log를 통해서 확인했습니다. 또한, 요청도 제대로 된 주소로 요청한 것을 볼 수 있습니다. 하지만, CORS policy로 Error가 발생했습니다.

 

 

4. CORS 란?


보통 Frontend와 Backend가 분리되어 있는 시스템의 경우에는 발생할 일이 없지만, Frontend와 Backend가 분리되어 있다면 많이 보셨을 단어입니다. CORS는 Cross-Origin Resource Sharing의 약자로... 단어그대로 본다면 교차출처리소스공유를 의미합니다.

 

보통은 Frontend로 서비스중인 환경에서 다른 도메인 즉 다른 주소의 Backend로 요청을 가능하게 허용하는 구조를 말하는데, 이렇게 교차출처에 대해서 요청하고 응답하는 것이 안전한 것인지 판단하여... 안전할 경우 이를 승락하는 방식을 정의하는 것 입니다. 

 

안전성에 대한 판단을 위해서는 어딘가에 이 출처로부터 온 Request는 안전하다고 정의가 되어있어야 합니다. 그렇다면 CORS는 어떤 상황에서 발생하는지... 어떤 경우가 Cross-Origin인지 알아보겠습니다. 

http://localhost:8888/qss/listPerUser?bsnsYear=2020&bsnsQuarter=1&userId=Ayotera+Lab

이 URL에는 많은 정보가 들어있습니다. 

 

  • Protocol - http
  • host - localhost (보통은 ip나 domain으로 되어 있음)
  • port - 8888
  • path - /qss/listPerUser
  • parameter - bsnsYear=2020&bsnsQuarter=1&userId=Ayotera+Lab

 

이 중에서, Origin에 해당하는 부분은 Protocol + host + port 입니다. 그렇다면 Cross-Origin에 해당하는 예시를 알아보도록 하겠습니다.

URL Cross-Origin 이유
http://localhost:8888/login No Protocol, host, port 동일
http://localhost:8888/login?userId=Ayotera+Lab No Protocol, host, port 동일
http://golocalhost:8888/ Yes host 불일치
http://localhost:8080/ Yes port 불일치
https://localhost:8888/login Yes Protocol 불일치

결국, 현재 구조상...

 

  • Frontend - http://localhost:8080
  • Backend - http://localhost:8888

 

으로 port 불일치 판정이며, 이를 위해서는 CORS policy 중 Access-Control-Allow-Origin 설정을 해줘야 합니다.

 

 

5. Access-Control-Allow-Origin


해당 설정을 해주는 방법은 여러가지가 있습니다. Frontend와 Backend가 있으니 적어도 2가지 방법이 존재하겠네요. 우선적으로 Frontend에서 적용하는 방법을 찾아서 해보았지만... 전혀 개선의 여지가 없어보였습니다.

 

[Frontend config]

// baseURL 기본값을 정의한다
axios.defaults.baseURL = 'http://localhost:8888/';
axios.defaults.headers.get['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers.get['Access-Control-Allow-Origin'] = '*';

위에서 baseURL을 설정하는 부분에 하단에 세부 설정을 추가합니다. 우선 저는 get방식에 대해서 우선 구현이 되어있기 때문에... 해당 설정에 header에 적용했습니다. 하지만 결과는 동일하게...

 

[Error]

Access to XMLHttpRequest at 
'http://localhost:8888/qss/listPerUser?bsnsYear=2020&bsnsQuarter=1&userId=Ayotera+Lab' 
from origin 'http://localhost:8080' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

이번에는 Backend에서 적용해 보겠습니다. springboot에서도 config를 통해서 CORS를 적용할 수 있습니다. springboot전역에서 구현하는 방법으로 WebMvcConfigurer를 implement한 class를 @Configuration annotation을 붙여서 적용합니다.

 

아마 기존에 vue project와 연동하고, SPA로 build된 index.html로 root url을 forward하기 위해서 아래의 설정을 하였을 것 입니다.

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
	
	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/").setViewName("forward:/index.html");
	}
    
}

여기에 @Override로 method를 하나 추가합니다.

@Override
public void addCorsMappings(CorsRegistry registry) {
	registry.addMapping("/**")
    	.allowedOrigins("*")
        .allowedMethods("GET", "POST")
        .maxAge(3000);;
}

addCorsMappings를 Override하면 여러가지 설정을 넣을 수 있습니다. 이중에서 가장 중요한 것은 allowedOrigins이고, 여러가지 세부사항도 함께 보도록 하겠습니다.

 

  • addMapping - CORS를 적용할 url의 패턴을 정의함 (/** 로 모든 패턴을 가능하게 함)
  • allowedOrigins - 허용할 origin을 정의함 (* 로 모든 origin을 허용함, 여러개도 지정가능)
  • allowedMethods - HTTP Method를 지정함 (* 로 모든 Method를 허용함)
  • maxAge - 원하는 시간만큼 request를 cashing함

 

우선 저는 모든 Origin에 대해서 allowed를 하였으며, GET와 POST에 대해서만 허용한 상태입니다. 그럼 서버를 다시 구동하여 테스트를 해보겠습니다.

(42) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, 
{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, 
{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, __ob__: Observer]

getQSSListPerUser End!!

오!!! 정상적으로 CORS policy의 위배없이 정상적으로 구동함을 확인할 수 있습니다.

 

 

6. Origin 지정


사실 allowedOrigin을 (*)로 지정하여 모두를 허용하면 보안의 문제가 발생할 수 있습니다. 따라서 요청하는 Origin을 지정해서 정의해야 합니다.

 

현재 Frontend의 Origin은 http://localhost:8080 이므로 아래와 같이 설정해줍니다.

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
//			.allowedOrigins("*")
			.allowedOrigins("http://localhost:8080")
        	.allowedMethods("GET", "POST")
        	.maxAge(3000);;
	}

구동을 하면, 역시 정상적으로 동작함을 확인할 수 있습니다.

 

- Ayotera Lab -

댓글