오라클로 백엔드 파일 배포

뚜우웅이·2025년 5월 17일

캡스톤 디자인

목록 보기
22/35

VM 인스턴스 생성


이미지는 ubuntu 22 버전을 선택해준다.

SSH 키는 다운 받아준다.

private 키는 절대 잃어버리거나 노출되면 안 된다.

수신 규칙 설정

내 인스턴스 - 보안 - 보안 목록 생성 - 수신 규칙 허용에 들어간다.


0.0.0.0/0은 외부 모든 IP 허용이다.
소스 포트 범위는 비워두면 된다.

Spring Boot API 서버 - 8080
웹 서비스용 Nginx -80 HTTP 기본 포트
HTTPS (인증서 적용) -443 HTTPS 기본 포트
프론트 Vue 개발 서버 - 5173, 3000, 8081 등 -개발 중 로컬 포트 (배포용 아님)
22 - 내IP주소/32 형태로 입력

위에 포트 번호를 다 열어준다.

이 후 네트워킹 - 서브넷 - 보안으로 가서 설정해둔 보안 목록을 추가한 뒤에
VNIC 에서 - IP 관리 - 보조 IP 주소 지정에서 예약된 퍼블릭 IP를 등록한다.

고정 IP 할당

예약된 퍼블릭 IPv4 주소로 들어가서 설정해준다.

인스턴스 연결

ssh -i {경로/Pprivate 키파일} ubuntu@{public IP주소}

ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@131.186.25.199

초기 설정

sudo apt update
sudo apt upgrade

타임존 설정

sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

2G 크기의 스왑 영역 만들기

sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

/etc/fstab 파일에 swapfile 등록

sudo vi /etc/fstab
/swapfile swap swap defaults 0 0

위의 내용을 입력하고 esc :wq!로 저장 후 빠져나온다.

스왑 영역 확인

free -m

ubuntu 방화벽 설정

ubuntu 방화벽에서 HTTP 80 포트 열기

sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT

443 포트 열기

sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT

8080 포트 열기

sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8088 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8081 -m state --state NEW,ESTABLISHED -j ACCEPT

MySQL 설치

sudo apt install mysql-server

MySQL root 계정 활성화

sudo mysql -u root
USE mysql;
UPDATE user SET plugin='mysql_native_password' WHERE User='root';
FLUSH PRIVILEGES;
exit;

비밀번호 신규 등록

mysqladmin -u root password

위 명령어 입력 후 비밀번호를 입력해준다.

MySQL 연결


1. 이름 입력
2. Standard TCP/IP over SSH 항목을 선택
3. 오라클 인스턴스 공개키 입력
4. 버튼을 클릭하여 서버의 private key 파일을 선택한다. (다운로드 받은 키 파일)
5. 127.0.0.1
6. 3306
7. root
8. 버튼을 클릭하고 mysql 관리자 비밀번호를 입력한다.

외부접속 허용

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf 파일에서 bind-address 수정한다.

재실행
sudo systemctl restart mysql

DB 생성

# MySQL에 접속
sudo mysql -u root -p
CREATE DATABASE freemarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

# 애플리케이션용 사용자 생성 (아직 없는 경우)
CREATE USER 'freemarket_user'@'localhost' IDENTIFIED BY '원하는_비밀번호';

# 사용자에게 권한 부여
GRANT ALL PRIVILEGES ON freemarket.* TO 'freemarket_user'@'localhost';

# 권한 적용
FLUSH PRIVILEGES;

# MySQL 종료
EXIT;

비밀번호는 ' 안에 들어가야 된다.

DNS 설정

https://www.duckdns.org/에 접속해서 등록한다.

등록한 domain name 확인

ssh -i 키파일 ubuntu@domain_name

ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@freemarket.duckdns.org

웹 서버

nginx

sudo apt install nginx

nginx의 설정 파일

sudo vi /etc/nginx/sites-available/tomcat.conf
# 80 포트는 HTTPS로 리디렉션
server {
    listen 80;
    server_name freemarket.duckdns.org;
    return 301 https://$host$request_uri;
}

# 443 포트는 실제 서비스
server {
    listen 443 ssl;
    server_name freemarket.duckdns.org;
    
    # SSL 설정 (certbot 관리)
    ssl_certificate     /etc/letsencrypt/live/freemarket.duckdns.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/freemarket.duckdns.org/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;
    
    # 추가 보안 헤더
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-XSS-Protection "1; mode=block";
    
    # WebSocket 프록시 설정 (채팅 기능)
    location ^~ /ws/chat/ {
        proxy_pass         http://127.0.0.1:8080/ws/chat/;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Authorization $http_authorization;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
    }
    
    # API 프록시
    location /api/ {
        proxy_pass         http://127.0.0.1:8080/api/;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Authorization      $http_authorization;
    }
    
    # OAuth2 관련 경로도 백엔드로 프록시
    location /oauth2/ {
        proxy_pass         http://127.0.0.1:8080/oauth2/;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Authorization      $http_authorization;
    }
    
    location /login/oauth2/ {
        proxy_pass         http://127.0.0.1:8080/login/oauth2/;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Authorization      $http_authorization;
    }
    
    # Swagger UI 관련 경로 설정
    location ~ ^/swagger-ui(.*)$ {
        proxy_pass             http://127.0.0.1:8080/swagger-ui$1;
        proxy_set_header       Host              $host;
        proxy_set_header       X-Real-IP         $remote_addr;
        proxy_set_header       X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header       X-Forwarded-Proto $scheme;
        proxy_buffer_size      128k;
        proxy_buffers          4 256k;
        proxy_busy_buffers_size 256k;
    }
    
    # API 문서 경로 설정
    location ~ ^/v3/api-docs(.*)$ {
        proxy_pass             http://127.0.0.1:8080/v3/api-docs$1;
        proxy_set_header       Host              $host;
        proxy_set_header       X-Real-IP         $remote_addr;
        proxy_set_header       X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header       X-Forwarded-Proto $scheme;
        proxy_buffer_size      128k;
        proxy_buffers          4 256k;
        proxy_busy_buffers_size 256k;
        add_header             Content-Type 'application/json';
    }
    
    # 정적 프론트엔드 파일
    location / {
        root       /home/ubuntu/freemarket-frontend/dist;
        index      index.html;
        try_files  $uri $uri/ /index.html;
    }
    
    # 백엔드에서 생성한 이미지 경로
    location /images/ {
        alias      /home/ubuntu/images/;
        expires    30d;
    }
    
    location /thumbnails/ {
        alias      /home/ubuntu/thumbnails/;
        expires    30d;
    }
}

freemarket.duckdns.org로 작성한다.

sudo nginx -t로 테스트 후
sudo systemctl restart nginx로 재시작한다.

tomcat 중계 설정 파일 활성화

sudo ln -s /etc/nginx/sites-available/tomcat.conf /etc/nginx/sites-enabled/

nginx 재시작

sudo service nginx restart

https 설정

certbot 설치

sudo apt install certbot python3-certbot-nginx

https://freemarket.duckdns.org 설정

sudo certbot --nginx -d freemarket.duckdns.org

이후 이메일 입력 후 다 Y를 입력한다.
https://freemarket.duckdns.org/ 로 접속

부록 SSH 연결


IntelliJ에 Remote Host를 클릭해서 사용한다.


... 을 누른 뒤 원하는 이름 입력 후 SFTP를 눌러준다.


... 누른 뒤 + 누른다.


Host는 우리가 할당받은 고정 IP한다.
Username에는 ubuntu를 입력(소문자로만), Authentication TypeKey pair을 골라준다. private Key file은 인스턴스 생성시에 받아놓은 private 키 파일을 설정해주면 된다.

Test Connection 버튼 눌러서 Success가 뜨면 SSH연결에 성공한 것이다.


이제 터미널로 조작이 가능하다.

백엔드(JAR 파일) 배포

jdk 설치

sudo apt install openjdk-21-jdk
java -version

방화벽

sudo ufw allow 22   # SSH
sudo ufw allow 80   # HTTP
sudo ufw allow 443  # HTTPS
sudo ufw allow 8080
sudo ufw enable

tomcat 중지

sudo systemctl stop tomcat9

JAR 파일 빌드

cd FreeMarket
./gradlew clean build
./gradlew clean build -x test

테스트 단계 없이 빌드

JAR 파일 전송

scp -i {key 경로} build/libs/FreeMarket-0.0.1-SNAPSHOT.jar ubuntu@freemarket.duckdns.org:/home/ubuntu/
scp -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key build/libs/FreeMarket-0.0.1-SNAPSHOT.jar ubuntu@freemarket.duckdns.org:/home/ubuntu/

기존 jar 파일 제거

rm -f /home/ubuntu/FreeMarket-0.0.1-SNAPSHOT.jar

8080 포트 확인

sudo lsof -nPi :8080

프로세스 종료

sudo kill PID

이미지 및 썸네일 디렉토리 생성

mkdir -p /home/ubuntu/images
mkdir -p /home/ubuntu/thumbnails
chmod 755 /home/ubuntu/images
chmod 755 /home/ubuntu/thumbnails

권한 주기

ls -la /home/ubuntu/ | grep images
ls -la /home/ubuntu/ | grep thumbnails

# 권한 변경 - 방법 1: 현재 사용자를 소유자로 설정
sudo chown -R ubuntu:ubuntu /home/ubuntu/images
sudo chown -R ubuntu:ubuntu /home/ubuntu/thumbnails

# 권한 변경 - 방법 2: 모든 사용자에게 읽기/쓰기/실행 권한 부여 (덜 안전한 방법)
sudo chmod -R 777 /home/ubuntu/images
sudo chmod -R 777 /home/ubuntu/thumbnails

백엔드 애플리케이션 실행

# 백그라운드로 실행
nohup java -jar /home/ubuntu/FreeMarket-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > /home/ubuntu/app.log 2>&1 &

# PID 확인 (선택사항)
echo $! > /home/ubuntu/app.pid
cat /home/ubuntu/app.pid

프론트엔드 파일 전송 (로컬에서 별도로 실행)

# 빌드
npm run build

# 서버의 기존 dist 폴더 내용 삭제
ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@freemarket.duckdns.org "rm -rf /home/ubuntu/freemarket-frontend/dist/*"

# 새 빌드 파일 업로드
scp -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key -r dist/* ubuntu@freemarket.duckdns.org:/home/ubuntu/freemarket-frontend/dist/

서버에서 확인

# 애플리케이션 실행 확인
ps aux | grep java

# 로그 확인
tail -f /home/ubuntu/app.log

# 파일 확인
ls -la /home/ubuntu/freemarket-frontend/dist/
ls -la /home/ubuntu/images/
ls -la /home/ubuntu/thumbnails/

Swagger Nginx 설정

소셜 로그인 URL 변경

application-prod.yml 변경

# baseUrl을 서버가 인식하도록 설정
server:
  forward-headers-strategy: native
  tomcat:
    remote-ip-header: X-Forwarded-For
    protocol-header: X-Forwarded-Proto
  • server.forward-headers-strategy: native
    Spring Boot가 Nginx, Apache, ELB 같은 리버스 프록시가 전달하는 헤더(X-Forwarded-*) 를 어떻게 처리할지를 지정한다.

  • server.tomcat.remote-ip-header: X-Forwarded-For

    • Spring Boot가 클라이언트의 실제 IP 주소를 어디서 얻을지 설정한다.
    • X-Forwarded-ForNginx 같은 프록시 서버가 클라이언트의 실제 IP를 전달할 때 사용하는 표준 헤더다.
    • 이 설정을 하면, request.getRemoteAddr() 등이 프록시의 IP가 아니라 진짜 사용자 IP를 반환하게 된다.
  • server.tomcat.protocol-header: X-Forwarded-Proto

    • Spring Boot가 요청의 프로토콜(HTTP/HTTPS) 을 어디서 얻을지 지정한다.
    • 프록시 서버(Nginx 등)는 실제 클라이언트가 http로 접속했는지 https로 접속했는지를 이 헤더로 전달한다.
    • 이 설정을 하면, Spring Security 등의 내부 로직에서 현재 요청이 HTTPS인지 정확히 인식할 수 있다.
    • 예를 들어 HttpServletRequest.isSecure()가 정확하게 true로 나다.

CORS 설정

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Value("${file.upload-dir}") private String uploadPath;
    @Value("${file.thumbnail-dir}") private String thumbnailPath;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // /api/** 경로에 대해 CORS 적용
                .allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org") // 허용할 출처 (Vue 개발 서버 주소)
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
                .allowedHeaders("*") // 모든 헤더 허용
                .allowCredentials(true) // 인증 정보(쿠키, JWT 등) 허용
                .maxAge(3600); // pre-flight 요청 캐시 시간 ()
        // 스웨거 UI 경로에 대한 CORS 설정 추가
        registry.addMapping("/swagger-ui/**")
                .allowedOrigins("*") // 또는 특정 도메인
                .allowedMethods("GET", "POST", "OPTIONS")
                .allowedHeaders("*")
                .maxAge(3600);
        // 필요시 다른 경로 추가 (: /oauth2/**)
        registry.addMapping("/oauth2/**")
                .allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org")
                .allowedMethods("GET", "POST", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
        registry.addMapping("/login/oauth2/code/**")
                .allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org")
                .allowedMethods("GET", "POST", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**")
                .addResourceLocations("file:" + uploadPath + "/"); // 경로 끝 '/' 중요
        registry.addResourceHandler("/thumbnails/**")
                .addResourceLocations("file:" + thumbnailPath + "/"); // 경로 끝 '/' 중요
    }
}

cors 설정에서 https://freemarket.duckdns.org을 추가해준다.

Security

                .cors(Customizer.withDefaults()) // WebConfig의 CORS 설정을 사용

프론트 배포 후 변경해야할 사항

# application.yml 또는 application-prod.yml
frontend:
  oauth:
    # 소셜 로그인 성공 후 토큰을 전달하며 리다이렉션될 Vue 페이지 경로
    callback-url: https://freemarket.duckdns.org/oauth/callback
  reset-password-url: https://freemarket.duckdns.org/oauth/callback

위의 주소는 프론트 배포 후에 적용한다.

profile
공부하는 초보 개발자

0개의 댓글