모두의 마당 해당 프로젝트에서 화상채팅 , 채팅 기능을 구현하기 위해, https 적용 및 웹소켓 연동을 진행했다. 로컬 환경에서는 문제가 없이 잘 진행됐지만, Ec2 환경에서 발생했던 에러를 해결했던 과정을 공유하고자 한다.(바보같았던 실수들이 많다..)
화상 채팅을 위해서는 https 설정이
필수이다. 설정을 하기 위해, SSL 인증서
를 발급 받아야 한다.
따라서 로컬 환경에서는
keytool -genkey -alias [인증서이름] -keyalg RSA -keysize 2048 -validity 700 -keypass [인증서패스워드] -storepass [저장소패스워드] -keystore [인증서파일명].jks
<sample>
keytool 도구를 이용해서 SSL 인증서를 생성하였다. 생성하고 스프링 서버에 SSL 관련 설정을 해줬다
package com.example.VideoChatting.config;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SslConfig {
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
// Add HTTP to HTTPS redirect : http 로 요청이 들어오면 https 로 리다이렉트
tomcat.addAdditionalTomcatConnectors(httpToHttpsRedirectConnector());
return tomcat;
}
private Connector httpToHttpsRedirectConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
문제 상황이 여러가지여서, 문제 상황을 아래 목차로 정리해봤다.
1.SSL 인증서 오류
2.ERR TOO MANY REDIRECTS
3.화상채팅 카메라 안보임
위에서 로컬환경에서 SSL를 인증서를 발급 받기 위해, keytool
을 사용했다.
하지만 EC2+Docker 배포 환경에서는 이런식으로 인증서를 인증 받을수 없었다.
그래서 무료 인증서를 발급 받을 수있는 Let’s Encrypt
에서 인증서를 받고, 여기서 인증서를 받기 위해서는 도메인
이 필요하다! 가비아에서 도메인을 받고 인증서를 발급 받은 후에, AWS Rout53
을 통해 호스팅 영역을 생성해볼 것이다.
호스팅 영역 생성
발급받으신 도메인을 도메인 이름에 입력해 생성하시면 됩니다.
래코드 세트 생성
네임서버 설정
NS유형의 레코드 4가지의 값을 도메인 등록 하신 곳에서 네임서버로 등록하시면 됩니다!!
nginx에서 사용하기 위한 SSL 인증서를 발급해보자. 우리가 흔히 아는 웹사이트들은 CA라는 루트 인증기관에서 인증서를 발급해주는데, 직접 발급하려면 Let's encrypt 로 발급할 수 있다(나를 내가 인증하는 식).
인증서를 발급하려면 도메인이 무조건 있어야 한다. IP만으로는 설정할 수 없다.
AWS에서 제공하는 public domain을 쓰면 안될까? 싶지만, AWS 도메인은 사실 임시로 부여하는 것들이라 보안 위험이 크기 때문에 애초에 LetsEncrypt에서 막아두었다.
Let’s encrypt에서 추천하는 방식인 Certbot을 사용하면 SSL 인증서를 쉽게 발급할 수 있다.
sudo yum install certbot
sudo yum install python-certbot-nginx # nginx용 플러그인 설치
sudo certbot --nginx -d <도메인 이름>
위 명령어를 실행하고 나면 /etc/letsencrypt/live/ 하위에 도메인 이름으로 된 폴더가 하나 생기고, SSL 인증을 위한 파일들이 만들어진다.
이 파일 경로를 nginx의 설정파일에 입력해주고, nginx 설정을 리로드해오면 모든 설정이 끝난다.
nginx 설치하고 나면 /etc/nginx
디렉터리가 생성된다. nginx의 루트 설정 파일은 /etc/nginx/nginx.conf
인데, 보통 해당 파일을 직접 수정하진 않고, 여러 하위 설정파일을 두고 이 파일에서 모듈처럼 로딩해서 사용한다. (코드에서 중복 제거하는 것 처럼) nginx.conf 파일을 보면 있는 include /etc/nginx/conf.d/*.conf 코드가 설정을 로딩해온다.
https 설정을 위해서 conf.d 폴더 하위에 domain-name.conf
같은 새로운 설정파일을 생성하고 아래와 같이 작성하자.
server {
listen 443 ssl;
server_name <도메인 이름>
location / {
proxy_pass http://127.0.0.1:<웹서버가 사용하는 포트>;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
# 아래 부분은 Let's encrypt를 통해 SSL 인증을 받은 키를 사용한다.
ssl_certificate /etc/letsencrypt/live/<SSL 발급받은 도메인 이름>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<SSL 발급받은 도메인 이름>/privkey.pem;
}
# http로 들어온 요청은 https로 리다이렉트 되도록 설정한다.
server {
listen 80;
location / {
return 301 <도메인 이름>$request_uri;
}
}
설정이 완료되면 nginx를 실행시켜준다.
sudo service nginx start # nginx 서버 실행하기
sudo nginx -t # nginx 설정 문법 및 인증서 테스트(반드시 루트 게정!)
sudo service nginx reload # 설정 파일을 수정했다면 설정파일을 로딩해온다.
이렇게 인증서를 정상적으로 발급받고 적용에 성공하였지만, 또다른 에러가 생겼다..
nginx에 인증서 위치를 정상적으로 등록하고, ec2 환경에서 화상 채팅 서비스 테스트를 해봤지만 00.00.000.00 리디렉션한 횟수가 너무 많습니다.
에러가 났다.
개발자 도구를 확인해보니, 무한대의 리다이렉트 요청이 생기고 있었다.
그래서 처음에는 아 내가 새로고침을 너무 많이 해서 오류가 생겼구나, 하고 쿠키를 삭제해봤지만 똑같이 에러가 떴다.
package com.example.VideoChatting.config;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SslConfig {
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
// Add HTTP to HTTPS redirect : http 로 요청이 들어오면 https 로 리다이렉트
tomcat.addAdditionalTomcatConnectors(httpToHttpsRedirectConnector());
return tomcat;
}
private Connector httpToHttpsRedirectConnector() {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(443);
return connector;
}
}
문제 원인은 이 SslConfig
파일이였다....
로컬 개발 환경에서
WebRtc
를 사용하기 위해,KeyTool로
인증서를 발급받고 8080포트로 오는 요청을443(Https)
로 리다이렉트 되게 했는데, 위에nginx
에서 리버스 프록시로 리다이렉트 설정을 해주고 있는데 또 톰캣서버에서 이중으로 리다이렉트를 해주고 있었다..... (바보같은 실수였다)
그래서 이 파일을 삭제하고, 다시 실행해보니 해당 에러가 없어졌다👍
하지만 ERR_TOO_MANY_REDIRECTS 를 해결했지만, 화상채팅시 카메라가 안켜져서 안보이는 에러가 났었다.
우선 결론 부터 말하자면, nginx에서 웹소켓 헤더 설정이 잘못됐었다.
Nginx는 버전 1.3부터 WebSocket을 지원하며, WebSocket의 로드 밸런싱 을 수행 할 수 있다.
HTTP에서 WebSocket으로 연결 전환시 HTTP의 Upgrade 및 Connection 헤더를 사용한다.
WebSocket을 지원할 때 리버스 프록시 서버가 직면하는 몇 가지 문제가 있다. 하나는 WebSocket이 hop-by-hop 프로토콜이므로 프록시 서버가 클라이언트의 Upgrade 요청을 가로챌 때 적절한 헤더를 포함하여 WAS 서버에 업그레이드 요청을 보내야 한다는 것이다. 또한 HTTP의 단기 연결과 달리 WebSocket은 오래 지속되기 때문에, 리버스 프록시는 연결을 닫지 않고 열린 상태로 유지하는 것을 허용해야 한다.
Nginx는 클라이언트와 WAS 간 터널(소켓)을 설정할 수 있도록 WebSocket을 지원한다. NGINX가 클라이언트에서 WAS로 업그레이드 요청을 보내려면 Upgrade 및 Connection
헤더를 명시적으로 설정해야 한다.
그래서 기존 아래 설정에서
location / {
proxy_pass http://[사용할 주소];
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_read_timeout 310s;
}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 헤더 설정을 추가해주니 카메라가 정상적으로 작동했다!!!
(필자는 해결하는데 많은 삽질 끝에 일주일정도 걸렸다.....)