우당탕탕 개발일지
[SSL] Nginx를 활용한 HTTPS → HTTP (feat. Mixed Content) 본문
프론트는 CloudFront와 S3로, 백엔드는 EC2 내에서 도커로 배포를 진행 후 접속을 했을 때 Mixed Content 에러가 발생하였다. 외부에서는 보안 이슈로 Https를 사용해야 하지만 내부 api 요청까지 굳이 Https로 할 필요는 없다 판단하고 Http로 진행하였지만 Https → Http 요청은 정책상 block 된다.
문제를 해결하기 위해 다음 두 가지 방법을 고려했지만 결론적으로 하나의 도메인으로 프론트, 백엔드 모두 배포하기 위해서는 CloudFront에 추가적인 API Gateway나 서브 도메인이 필요하다. 나는 이 두가지를 모두 진행해보고 비용을 아끼고자 무료 도메인을 발급하였다.
1. Public IP에 SSL 인증서 적용
우리가 흔히 사용하는 cerbot은 도메인에만 발급해주지 IP에 SSL인증서를 발급해주지는 않는다. IP에 SSL 인증서를 발급해주는 사이트가 있지만 비싸서 차라리 가비아에서 도메인 하나 더 사는게 이득이다.
2. CloudFront 도메인을 EC2로 라우팅
여기서 삽질을 굉장히 많이 했다. 기존 도메인에 ec2 IP 주소를 연결해주고 nginx로 Https → Http 변환 작업을 진행하였다. 하지만 서버에 요청만 시도하면 MethodNotAllowed 오류가 발생하였다.
root@ip-172-31-47-158:/home/ubuntu# curl -X POST https://doctorforu.online/hospital-service/hospitalsList -H "Content-Type: application/json" -H -d '{"key": "value"}' <?xml version="1.0" encoding="UTF-8"?> <Error><Code>MethodNotAllowed</Code><Message>The specified method is not allowed against this resource.</Message><Method>POST</Method><ResourceType>OBJECT</ResourceType><RequestId>AHWJSNRPXGZ7JP8V</RequestId><HostId>Tn5ZQgLJKtJ8B0cOVklXTNKQ29rlYJqei6Wr8A8/nHPxRpJSgPEuxgcesYzf5K9mH9/OR7CSxTU=</HostId></Error>
이 오류는 요청한 POST 메소드가 허용되지 않음을 의미한다.
2-1. CloudFront 설정 확인
CloudFront에서 POST 메소드가 허용되어 있는지 확인해준다.
2-2. Spring Boot 설정 확인
다음으로 SpringBoot에서 Cors 관련한 모든 설정을 수정해주었다.
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "https://doctorforu.online")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
CorsFilter
@Configuration
public class CorsFilterConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Arrays.asList(
"https://doctorforu.online", // 특정 도메인
"http://localhost:3000" // 로컬 개발 환경 도메인
));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(Arrays.asList("X-Requested-With", "Content-Type", "Authorization", "X-XSRF-token"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
2-3. Nginx 설정
POST 요청이 도커 내 SpringBoot까지 도달하지 못하여 Nginx 측의 문제라 판단하고 설정을 계속해서 수정하였다.
nginx.conf
server {
listen 443 ssl;
server_name doctorforu.online www.doctorforu.online;
ssl_certificate /etc/letsencrypt/live/www.doctorforu.online/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.doctorforu.online/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location /favicon.ico {
allow all;
log_not_found off;
access_log off;
}
location /.well-known/acme-challenge/ {
root /var/www/html;
allow all;
}
# API 요청에 대한 프록시 설정
location /hospital-service/ {
proxy_pass http://localhost:8080; # 내부 Docker 서비스
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Authorization 헤더 전달
proxy_set_header Authorization $http_authorization;
# Content-Type 헤더 전달
proxy_set_header Content-Type $http_content_type;
# X-Forwarded-For, X-Forwarded-Proto 추가
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SPA 라우팅 설정
location / {
try_files $uri /index.html;
error_page 405 =200 /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
server {
listen 80;
server_name www.doctorforu.online;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# HTTP 요청을 HTTPS로 리디렉션
location / {
return 301 https://$host$request_uri;
}
}
안해본 설정이 없었고, 혹시나 싶어서 GET 요청을 날렸을 때 원인을 찾을 수 있었다.
root@ip-172-31-47-158:/home/ubuntu# curl -X GET https://doctorforu.online/user-service/register/testid123 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" <?xml version="1.0" encoding="UTF-8"?> <Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key><ServerIP/index.html</Key><RequestId>MWGXQKJDP2DV91FZ</RequestId><HostId>x8yLbZi5uH1GpuytBW5nIvcHsoml0gmJPKTd08psqKv6jW208lGdVV0L2c0mK59L/gM9dStPXhk=</HostId></Error>
요청이 S3에 연결된 CloudFront로 전달되었고, S3에서 요청한 키(Server IP/index.html)를 찾을 수 없다는 응답을 받았다. 즉, 요청이 Nginx → Docker Container로 전달되지 않고 CloudFront가 S3로 라우팅하고 있었고, CloudFront 경로 설정이 문제였다.
** 참고 **
Route 53을 이용하여 호스팅 영역을 지정해주면 가비아에서 DNS 설정이 불가능하다.
위와 같이 ec2 Public IP를 값으로 지정해주고 이틀넘게 기다렸는데도 되지 않더니 AWS에서 레코드로 지정해주자 1분만에 설정이 되었다.
무료 도메인 사용
CloudFront에서 origin을 ec2 public IP로 설정해준 뒤 경로 설정으로 하면 된다고는 하지만 ec2가 origin으로 등록이 안되었다. 알고보니 API Gateway를 추가 설정해줘야 ec2가 등록이 가능하다 하였고, 굳이 AWS ELB를 사용하여 추가 금액을 지불할 바에는 도메인을 새로 사는게 낫다고 판단하였다.
내도메인.한국 - 한글 무료 도메인 등록센터
한글 무료 도메인 내도메인.한국, 웹포워딩, DNS 등 무료 도메인 기능 제공
xn--220b31d95hq8o.xn--3e0b707e
가비아 외에 무료로 사용할 수 있는 도메인 사이트를 사용하였다. 단점은 가비아에서 사는 도메인 주소와 달리 이쁘지 않다. 나의 경우 실제 서비스 상에서 보여지는 도메인은 가비아로 구매하였고, 해당 도메인은 내부에서 사용할 거라 상관이 없었다.
도메인 등록
회원가입 진행 후 이용이 가능하다. 원하는 도메인을 등록한 후 메인 관리 페이지로 이동하여 설정한다.
IP연결(A)에 연결할 ec2 Public IP주소를 입력하면 등록이 완료된다. 전파 속도는 가비아보다 훨씬 빨랐다. ec2에서 다음 명령어를 치면 사용 가능한지 확인 가능하다.
dig doctorforu.kro.kr A
status가 NXDOMAIN이면 DNS 등록이 아직 안되었음을 의미한다.
선수 작업
Nginx가 설치되어있다는 가정하에 nginx.conf 파일에 등록할 도메인을 미리 입력해준다. Cerbot은 Nginx 설정 내에서 서버 블록의 server_name으로 인증서를 자동으로 적용해주기 때문에 필요하다.
nginx.conf
server {
listen 80;
server_name <Domain>r;
# 인증서를 발급받을 때 HTTP 요청 처리
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
Cerbot 설치
sudo apt update
sudo apt install certbot python3-certbot-nginx
SSL 인증서 발급
sudo certbot --nginx
SSL 인증서 발급 여부 확인
sudo certbot certificates
Nginx 설정
nginx에 SSL 인증서를 연동해준다.
nginx.conf
server {
listen 443 ssl;
server_name doctorforu.kro.kr;
ssl_certificate /etc/letsencrypt/live/doctorforu.kro.kr/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/doctorforu.kro.kr/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
# 쿠키 경로 재설정
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
proxy_set_header Cookie $http_cookie;
# 헤더 전달
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 https;
# Authorization 헤더 전달 (존재할 경우)
proxy_set_header Authorization $http_authorization;
# Connection 유지
proxy_set_header Connection keep-alive;
}
}
JWT를 사용하여 Http Only를 적용하였기에 Cookie 설정과 Authorization 설정을 추가로 해주었다. 두 설정이 없으면 401 에러가 발생한다.
Nginx 재시작
sudo nginx -t # nginx 실행 테스트
sudo systemctl reload nginx
프론트 쪽에서도 요청 api를 포트 번호를 제외한 새로 발급받은 도메인으로 바꿔주면 완료된다.
'Cloud' 카테고리의 다른 글
[MSA] Docker-Compose(2) (1) | 2024.12.17 |
---|---|
[MSA] Dcoker-Compose (1) (4) | 2024.12.15 |
[AWS] ECR + Lambda + API Gateway(feat. Cold Start) (1) | 2024.12.11 |
[AWS] S3 + CloudFront + Route53 (feat. AccessDenied) (1) | 2024.12.10 |
Typescript + Firebase 환경 설정 (0) | 2024.08.11 |