SSL (HTTP2/H2) 조사 및 적용
이번 포스팅에서는 HTTP2 / H2 적용을 위한 SSL 에 대해서 조사하고, 적용 방법에 대해서 작성해 보도록 하겠습니다.
HTTP/2 Protocol
1. HTTP/2 Protocol 이란
-
HTTP/2 는 HTTP/1 의 확장으로 기존의 HTTP/1 과의 호환성을 유지하며 성능에 초첨을 맞춘 프로토콜
- Multiplexed Streams
- HTTP/2는 하나의 TCP 연결을 통해 여러 데이터 요청을 병렬로 전송할 수 있다.
- HTTP/2는 Multiplexed Streams를 이용하여 Connection 한 개로 동시에 여러 개의 메시지를 주고 받을 수 있으며 응답은 순서에 상관없이 Stream으로 주고 받는다. 따라서 RTT 시간이 줄어들어 별도의 최적화 과정이나 도메인 샤딩없이 웹 사이트 로드 속도가 빨라진다. HTTP/1.1의 Connection Keep-Alive, Pipelining의 개선된 것을 알 수 있다.
- Header Compression
- HTTP/2는 중복 헤더 프레임을 압축해서 전송한다.
- 클라이언트와 서버에서 모두 이전 요청에 사용된 헤더 목록을 유지관리한다.
- Binary protocol
- 텍스트 프로토콜에서 바이너리 프로토콜로 변화
- 기존 HTTP/1에서 사용된 frame의 복잡성을 편리하게 해주고, 텍스트와 공백들이 섞여 혼동이 발생하던 명령들보다 명령어를 단순하게 구현할 수 있다.
- HTTP/2 구현을 사용하는 브라우저는 네트워크를 통해 전송하기 전에 동일한 텍스트 명령을 바이너리로 변환한다.
사설 SSL 인증서 생성
1. 사설 SSL 인증서란
- SSL 인증서는 기본적으로 공용 인터넷 환경을 기준으로 하기 때문에 즉, 사설/폐쇄 환경를 고려하지 않음
- 따라서 사설 SSL 인증서는 공인 인증 기관이 아닌 특정 조직에서 발급
- 이런 인증서는 인터넷으로 서비스되는 웹서버가 아니라 기업 내 폐쇄망에서 직원들이 접속하는 웹서버에서 주로 사용
2. 사설 SSL 인증서 생성 : openssl 사용
- 루트 인증서(CA) 생성
// rootCA.key 파일 생성 openssl genrsa -out rootCA.key 2048 // rootCA.key 파일 내용 확인 cat rootCA.key
- 루트 인증서 CSR(인증 서명 요청) 파일 생성
// rootCA.csr 파일 생성 openssl req -new -key rootCA.key -out rootCA.csr // rootCA.csr 파일 내용 확인 cat rootCA.csr
- 루트 인증서(CA)를 만들고 자체 서명
// rootCA.crt 파일 생성 openssl x509 -req -sha256 -days 999999 -in rootCA.csr -signkey rootCA.key -out rootCA.crt // rootCA.crt 파일 내용 확인 cat rootCA.crt
- 서버 인증서(CA) 생성
// server.key 파일 생성 openssl genrsa -out server.key 2048 // server.key 파일 내용 확인 cat server.key
- 서버 인증서 CSR(인증 서명 요청) 파일 생성
// server.csr 파일 생성 openssl req -new -key server.key -out server.csr // server.csr 파일 내용 확인 cat server.csr
- 서버 인증서(CA)를 만들고 자체 서명
// server.crt 파일 생성 openssl x509 -req -sha256 -days 999999 -in server.csr -signkey server.key -out server.crt // server.crt 파일 내용 확인 cat server.crt
- 웹 서버에 SSL 인증서 적용
// CA 인증서를 포함하는 서버 인증서 생성 cat server.crt rootCA.crt > server.pem
Next.js 에서 HTTP2/H2 적용 (Node Server 사용)
1. 적용과정
- server.js 파일 생성 (구글의 spdy 사용)
const { createServer: http } = require('http'); const { parse } = require('url'); const next = require('next'); const path = require('node:path'); const express = require('express') const compression = require('compression') const spdy = require('spdy'); const fs = require('node:fs'); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); const ports = { http: 3001, https: 3443, }; const httpsOptions = { key: fs.readFileSync(path.join(__dirname, '/server-key.pem')), cert: fs.readFileSync(path.join(__dirname, '/server.pem')) }; const shouldCompress = (req, res) => { // don't compress responses asking explicitly not if (req.headers['x-no-compression']) { return false } // use compression filter function return compression.filter(req, res) } app.prepare().then(() => { http((req, res) => { const parsedUrl = parse(req.url, true); handle(req, res, parsedUrl); }).listen(ports.http, (err) => { if (err) throw err; console.log(`> HTTP: Ready on http://localhost:${ports.http}`); }); // create the express app const expressApp = express(); // set up compression in express expressApp.use(compression({ filter: shouldCompress })) // declaring routes for our pages expressApp.get('/', (req, res) => { return app.render(req, res, '/', req.query) }) // fallback all request to next request handler expressApp.all('*', (req, res) => { return handle(req, res) }) spdy.createServer(httpsOptions, expressApp).listen(ports.https, error => { if (error) { console.error(error) return process.exit(1) } else { console.log(`> HTTPS: Ready on https://localhost:${ports.https}`) } }) });
- package.json 파일 수정
"scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "secure": "node server.js", // 실행 명령어 추가 },
- 실행 - 터미널에서 실행
npm run secure
Nginx 를 이용한 HTTP2/H2 적용
1. 적용과정
- docker-file 생성 (dockerfile-nginx)
FROM nginx:1.23.3-alpine COPY ./nginx/nginx.conf /etc/nginx/nginx.conf EXPOSE 443 EXPOSE 50003 EXPOSE 50008
- docker compose 파일 생성 (docker-compose.yml)
version: '3' services: nextjs: build: context: . dockerfile: ./docker/dockerfile-front image: emma_front_ssl:latest container_name: emma-front-ssl ports: - "50003:3000" command: "npm run start" environment: TZ: "Asia/Seoul" # 한국 시간대 설정 networks: - front_net nginx: build: context: . dockerfile: ./docker/dockerfile-nginx image: emma_nginx:latest container_name: emma-nginx ports: - "50008:80" - "443:443" volumes: - /etc/pki/ca-trust/source/anchors/server.pem:/etc/nginx/ssl/server.crt - /etc/pki/ca-trust/source/anchors/server.key:/etc/nginx/ssl/server.key environment: TZ: "Asia/Seoul" depends_on: - nextjs networks: - front_net networks: front_net:
- docker 실행
docker compose -f docker-compose.yml up -d --build
참고 URL