Spring Security + JWT + React - 09. AWS EC2 React 프론트엔드 배포 + CORS 해결

june·2022년 9월 29일
1
post-thumbnail
post-custom-banner

프론트 엔드의 배포 과정 또한 이전 글에서 메모리 스와핑까지의 과정을 거치면 된다.

CORS 수정

이 후의 과정에서, 몇개의 수정과정을 거쳤다.

가장 중요한 과정은 CORS 에러를 해결하는 방법이다.

우리는 이전에 이것을 리액트의 Webpack Dev Server 라이브러리의 프록시 서버를 이용하여 해결했다.

package.json

{
  ...
  "proxy":  "http://localhost:4000"
}

그러나 Webpack Dev Server는 개발과정에서만 운용이 되고, 배포과정에서는 실행이 어렵다. 따라서 이러한 CORS 에러의 해결을 백엔드 과정에서 해결하도록 한다.

백엔드 - CORS

컨트롤러에서 CrossOrigin 어노테이션을 이용할 수도 있지만, 컨트롤러의 수가 하나가 아니므로, 모두에게 적용될 수 있도록 config 파일에서 CORS 정책을 설정해보자.

우리는 Spring Security를 사용했으므로, 우리의 config파일은 WebSecurityConfig다.

/config/WebSecurityConfig.java

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@Component
public class WebSecurityConfig {
    ...

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors().configurationSource(corsConfigurationSource())

                .and()
                ...
                ...
                .and()
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                .antMatchers(...
                ...
                

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();

        config.addAllowedOrigin("http://localhost:3000"); // 로컬 
        config.addAllowedOrigin("http://프론트 AWS  주소"); // 프론트 IPv4 주소
        config.addAllowedMethod("*"); // 모든 메소드 허용.
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);

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

기본적인 구조는 corsConfigurationSource메서드를 통해 설정을 거친 CorsConfigurationSource를 반환하여 filterChain 메서드의 HttpSecurity 값에 적용시키는 것이다.

이후 requestMatchers 부분은 CORS preflight요청은 인증처리를 하지 않겠다는 뜻인데, 이 부분은 복잡하니 나중에 설명을 하겠다. 간단히 설명하자면, CORS요청 전송 이전에 서버에서 어떤 origin과 method를 허용하는지 알려주는 요청인데, 이는 401 응답을 하지 않겠다는 의미로 보면 된다. 이 과정이 있어야 CORS요청이 정상적으로 이루어진다.

먼저 CorsConfiguration 객체를 만들고, 허가해 줄 Origin (출처, protocol + host + port)를 추가해준다.

우리는 여기서 기본적으로 테스트 해볼 http://localhost:3000과 앞서 발급한 프론트의 인스턴스 주소를 넣어준다.

또한 모든 HTTP 메소드도 허용해주고, 모든 헤더도 허용해준다.

이후 우리는 쿠키를 통한 인증과정을 거치므로 Credentials도 허가해준다. 이후 새로운 source 객체를 만든 다음, config 객체를 적용해준 다음 반환하면 된다.

이후 변경한 Spring 프로젝트를 백엔드 인스턴스에서 git pull을 통해 업데이트 한 다음 재빌드하거나

혹은 vi 명령어를 통해 수정한 다음 재빌드하면 된다.

프론트엔드 - URL 수정

프론트엔드에선 일단 CORS 문제를 백엔드에서 해결했으므로, package.jsonproyx부분을 지워준다.

이후 백엔드의 주소를 프론트 앱 전체에 적용해주기 위해 따로 유틸리티 파일을 만들었다.

/utility/uri.ts

export const URI = "http://백엔드 주소";

이후 이를 전체 통신을 담당하는 fetch-action.ts에 적용시켜준다,

/store/fetch-action.ts

import { URI } from '../utility/uri';
...
const uri = URI;

const fetchAuth = async (fetchData: FetchData) => {
  ...
  try {
    const response:AxiosResponse<any, any> | false =
    (method === 'get' && (await axios.get(uri + url, header))) ||
    (method === 'post' && (await axios.post(uri + url, data, header))) ||
    (method === 'put' && (await axios.put(uri + url, data, header))) ||
    (method === 'delete' && (await axios.delete(uri + url, header))
    );
    ...
  }

AWS 배포

포트 설정

이전 과정과 메모리스와핑 까지는 똑같이 따라와도 되나, 포트는 톰캣을 사용하는 백엔드서버와 달리 nginx를 이용할 것이므로 다르게 설정한다.

기존 22포트 이외에 80포트만 열어주면 된다.

nginx 설치

리액트 앱은 톰캣이 존재하는 스프링과 달리 스스로 배포하기가 힘들기 때문에 nginx를 통해 배포를 진행할 것이다.

$ sudo apt install nginx

nginx를 설치해준다.

$ sudo apt-get install systemd

이후 ubuntu에서 BSD init 대신 프로세스를 관리하는 init 시스템인 systemd를 설치해준다.

build

$ sudo apt install npm

노드 패키지 매니저를 설치한다.

$ git clone https://github.com/[리액트 저장소]

리액트 프로젝트를 가져오자. 이후 해당 프로젝트로 이동한다

$ npm run build

빌드를 시작한다.

이런 화면이 나왔다면 빌드가 성공적으로 진행된 것이다.

배포

nginx conf

이제 nginx를 빌드한 리액트 앱과 연결해주는 과정을 거치자.

nginx의 설정을 건드리려면 nginx.conf라는 파일을 수정하면 된다. 수정해보자

$ sudo vi /etc/nginx/nginx.conf

여기서 아예 작성해서 nginx와 리액트를 연결할 수도 있지만, 좀 더 깔끔하고 자주 이용하는 방식을 사용하자.

여기서 위처럼

include /etc/nginx/sites-enabled/*.conf;

문장을 추가하면 된다. 이렇게 되면, /etc/nginx/sites-enabled이라는 디렉토리에 있는 conf파일을 적용한다는 뜻이 된다.

만약

server {
	listen	80 default_server
    ...
}

와 같은 부분이 활성화 되어있다면, 이부분을 전부 주석처리 해야한다. 필자가 설치한 1.18.0 버전에는 이 부분이 존재하지 않았다.

$ sudo mkdir /etc/nginx/sites-available
$ sudo mkdir /etc/nginx/sites-enabled

앞서 말한 /etc/nginx/sites-enabled 디렉토리는 존재하지 않으므로 생성한다. 그리고 또 /etc/nginx/sites-available 또한 생성할 것이다.

그렇다면 이 디렉토리는 왜 생성하는가?

/etc/nginx/sites-available에서 만든 conf파일을 Symbolic link해서 /etc/nginx/sites-available에 생성하는 방식으로 만들 것이기 때문이다.

여기서 Symboilic link는 윈도우의 바로가기와 유사하다고 보면 된다.

그렇다면 왜 이런 방식을 쓰는가? 만약 해당 인스턴스의 Nginx에서 이 리액트 서비스 말고 더 많은 서비스를 운영하고 있다고 가정해보자.

만약 /etc/nginx/sites-enabled에만 파일을 작성했다면, 이런 모양일 것이다. 그런데 여기서, 특정 서비스 몇개를 잠시만 종료하고 싶다고 가정하자. 그러면 여기의 conf파일을 지우면 되는데, 그러면 따로 백업을 하지 않는 이상 conf파일은 영영 없어지게 된다.

다음은 우리가 하는 Symbolic link의 방식이다. 이 경우에서 몇몇 서버를 종료하고 싶으면 어떻게 하면 되는가?

이렇게 단순히 /etc/nginx/sites-enabled에서 Symbolic link만 삭제 하면 종료가 되고, 원본 설정 파일도 존재한다. 다시 서버를 시작하고 싶으면 Symbolic link만 설정한다면 다시 실행할 수 있는 것이다.

conf 작성

이제 작성해서 연결해보자.

$ sudo vi /etc/nginx/sites-available/[원하는 이름].conf
server {
	listen 80;
	location / {
		root /home/user/[리액트 프로젝트 이름]/build;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}

이렇게 작성하면 된다.

해당 내용을 하나하나 보면

  • listen: 포트에 대한 설정. 80포트 사용
  • location /: URL에 대한 설정. '/'이 포함된 경로에 대한 설정이므로, 80포트로 오는 모든 연결을 의미함.
  • root: 실행할 파일들의 루트 위치를 의미하며, 빌드한 파일의 경로를 입력하면 된다.
  • index: 말 그대로 인덱스 파일을 의미한다. 빌드안에 들어간 파일들이 실행되게 된다
  • try_files: 정적 파일에 관한 설정으로, 만약 정적 파일이 없는 uri일 경우 index.html을 리턴하도록 설정한 것이다.

이제 심볼릭 링크를 설정하자.

$ sudo ln -s /etc/nginx/sites-available/[원하는 이름].conf /etc/nginx/sites-enabled/[원하는 이름].conf

이렇게 되면 /etc/nginx/sites-available/conf파일을 참조한 심볼릭 링크 파일이 생성된다.

설정파일이 제대로 되었는지 체크해보자

$ sudo nginx -t

제대로 설정되었다.

nginx 실행

$ sudo systemctl start nginx

nginx를 실행해보자

그러면 이제 프론트엔드 인스턴스의 IP 주소로 접속해보자.


정상적으로 실행이 된다!

참고로 nginx를 멈추는 명령어는 다음과 같다

$ sudo systemctl stop nginx
profile
초보 개발자
post-custom-banner

0개의 댓글