
GoDoc: net/http package
How to set up HTTPS on golang web server?
Making HTTP requests in Go
웹 개발에서의 Redirect: 페이지 이동과 사용 방법
자체 서명 사설 SSL 인증서 만들기
https 제공을 위한 인증된 SSL과 서버 개인 키 생성하기
[Server] Go Web Server HTTPS 적용하기
openssl 없이 Go HTTPS 서버 실행해보기
인증서 체인
Certificate Chain
리다이렉트(Redirect) | 토스페이먼츠
CHATGPT
11/29~12/3 스터디
http 통신은 net 패키지로 했던 기억이 나는데.. https 통신도 가능할까?
이전 시간에 알아본 http/https 프로토콜에서 https 통신을 이루기 위해서는 CA의 인증서가 팔요할텐데,,
Go 언어에서 HTTP 통신을 처리하기 위해 net/http 패키지를 사용한다.
이 패키지는 HTTP 클라이언트와 서버를 구현하기 위한 다양한 기능을 제공한다.
아래 코드를 실행하면,
터미널에 Server is running on :8080 가 출력되고,
브라우저에서 http://localhost:8080으로 접속하면 "Hello, World!" 메시지를 확인할 수 있다.
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!") // 응답 메시지 작성
}
func main() {
http.HandleFunc("/", handler) // 요청 경로와 핸들러 연결
fmt.Println("Server is running on :8080") // 서버 시작 로그 출력
http.ListenAndServe(":8080", nil) // HTTP 서버 실행
}

/로 시작하는 모든 경로와 매칭HTTP 요청을 보내고 응답을 받는 클라이언트의 예제이다.
GET 요청을 보내고, 상태 코드와 응답 본문을 출력한다.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://velog.io/@jiyunwoo/Go-vs.-Rust-1") // GET 요청
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close() // 응답 본문 닫기
body, err := io.ReadAll(resp.Body) // 응답 본문 읽기
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Println("Response Status:", resp.Status) // 응답 상태 출력
fmt.Println("Response Body:", string(body)) // 응답 본문 출력
/* 출력 예시
Response Status: 200 OK
Response Body: <!doctype html> ~~
*/
}
위 예제에서 사용된 주요 메서드들에 대해 알아보자.
내용은 패키지 문서에서 가져왔다.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc registers the handler function for the given pattern.
A ResponseWriter interface is used by an HTTP handler to construct an HTTP response.
A Request represents an HTTP request received by a server or to be sent by a client.
The documentation for ServeMux explains how patterns are matched.
ServeMux is an HTTP request multiplexer. (요청 URL과 등록된 핸들러를 매칭)GET /static/ matches a GET request whose path begins with /static/. → 어떤 요청 URL이 어떤 핸들러와 연결될지 관리작동 방식
- URL 패턴과 그에 대한 핸들러 미리 등록 → 요청 들어오면, 요청 URL을 등록된 패턴과 비교하여 핸들러 찾아 실행 → 핸들러 호출해 요청 처리
- 패턴이 여러 개 등록된 경우, 요청 경로와 가장 구체적으로 일치하는 패턴의 핸들러가 실행됨!
/static/는/static/로 시작하는 모든 경로와 매칭 → 슬래시(/)로 끝나는 패턴은 경로의 하위 URL도 처리/static처럼 슬래시가 없는 경우, 경로가 정확히/static인 경우에만 매칭
func ListenAndServe(addr string, handler Handler) error
HTTP 서버 시작 함수, 인자로 서버 주소와 요청을 처리하는 핸들러(보통 nil로 기본값) 설정
ListenAndServe starts an HTTP server with a given address and handler.
func Get(url string) (resp *Response, err error)
Get issues a GET to the specified URL.
If the response is one of the following redirect codes, Get follows the redirect, up to a maximum of 10 redirects:
non-2xx response doesn't cause an error.+) HTTP status codes as registered with IANA.

Go 언어에서 HTTPS 통신을 구현하려면 TLS(SSL) 인증서를 사용하여
net/http 패키지의 http.ListenAndServeTLS 함수로 HTTPS 서버를 실행할 수 있다.
HTTPS는 TLS/SSL을 통해 데이터를 암호화하여 보안 통신을 제공한다.
HTTPS를 구현하려면 다음 두 가지 파일이 필요하다:
cert.pem) : 서버의 공개 키, 서버의 도메인 이름, CA 정보, 만료 날짜 등 정보 포함!key.pem) : 서버의 개인 키 ← 이 파일은 서버에서만 사용되며 외부로 유출되서는 안 된다!openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365


테스트 환경에서는 자체 서명 인증서를 사용하거나, HTTPS 클라이언트에서 InsecureSkipVerify: true 옵션을 사용해 인증서 검증을 비활성화할 수 있다.

→ 하지만 실제 서비스에서는 절대 사용하지 말아야 한다!
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365
req : OpenSSL의 인증서 서명 요청(Certificate Signing Request, CSR)을 생성하거나 인증서를 발급할 때 사용-x509 : X.509 형식의 인증서를 생성-newkey rsa:2048 : 새로운 RSA 키 쌍(공개 키와 개인 키)을 생성, 키 길이를 2048비트로 설정-nodes : 개인 키(Private Key)를 암호화하지 않고 편리하게 저장 → 기본적으로는 개인 키 암호화해 파일로 저장-keyout key.pem : 생성된 개인 키를 key.pem 파일로 저장-out cert.pem : 생성된 인증서를 cert.pem 파일로 저장-days 365 : 인증서의 유효 기간을 365일로 설정
→ 리눅스 환경에서 위 명령어를 실행해본 결과, cert.pem과 key.pem이 만들어졌다! 신기하다,,


+) Snippet 참고

아래는 HTTPS 서버 구현 예제이다.
package main
import (
"fmt"
"net/http"
)
func handler2(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, Secure World!")
}
func main() {
http.HandleFunc("/", handler2) // 요청 처리 핸들러 등록
fmt.Println("Starting HTTPS server on :8443") // HTTPS 서버 실행 로그
err := http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}
위 코드를 실행한다.
브라우저에서 https://localhost:8443로 접속한다.
인증서가 자체 서명된 것이므로 브라우저에서 경고 메시지가 표시될 수 있다.
아래는 직접 실행해본 결과이다.

→ 고급 → 안전하지 않음 으로 이동

→ Hello, Secure World! 가 출력되는 것을 볼 수 있다.

→ 인증서 정보도 확인할 수 있다. 유효 기간이 365일로 설정되었고, 인증서와 서버의 공개키 등등을 직접 확인해볼 수 있었다.
HTTPS는 ListenAndServeTLS로 TLS를 활성화하는 것을 제외하면 HTTP와 거의 동일하게 구현할 수 있다.
핵심 차이점은 통신이 암호화된다는 점이다.
나머지 라우팅, 핸들러 설정, 요청/응답 처리 방식은 모두 동일하다.
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

HTTPS 서버를 시작하는 함수 ▷ 인자: 서버 주소, 인증서 파일 경로, 키 파일 경로, 요청을 처리할 핸들러.
ListenAndServeTLS acts identically to ListenAndServe, except that it expects HTTPS connections.
Additionally, files containing a certificate and matching private key for the server must be provided.
- 클라이언트(예: 브라우저)는 서버 인증서를 신뢰하려면,
그 인증서가 신뢰할 수 있는 루트 인증서로부터 서명되었음을 확인해야 한다. 이때 중간 인증서들이 필요!
- 서버 인증서 → 중간 인증서 → 루트 인증서 순서로 인증서 체인이 완성
- 중간 인증서가 누락되면, 클라이언트는 서버 인증서만 가지고는 신뢰할 수 없어서 "인증서 체인이 불완전하다"는 오류가 발생할 수 있다.
→ 서버에서cert.pem파일을 사용할 때는 서버 인증서, 중간 인증서, 루트 인증서가 모두 포함되어야 한다.- 만약 서버가 중간 인증서를 누락하면, 클라이언트는 루트 인증서까지 도달하지 못해 "인증서 체인이 불완전하다"는 오류가 발생.
cat server.crt intermediate.crt root.crt > cert.pem
각각 인증서를 발급하여 합쳐서 제공하는 경우도 있지만, 별도의 파일을 제공하는 경우도 있다.
후자라면, 사용자가 이 인증서들을 연결하여 하나의 파일로 만들어야 한다.




루트 CA가 스스로 서명하여 루트 인증서 발급 → 루트 CA가 중간 인증서 생성 및 자신의 비공개키로 서명 → 중간 CA가 서버 인증서 생성 및 자신의 비공개키로 서명 → 서버가 클라이언트에게 서버 인증서와 중간 인증서 제공, 중간 인증서를 루트 인증서와 연결하여 신뢰 체계 검증
0. 중간 CA와 루트 CA
루트 CA (Root Certificate Authority): 인증 체계의 최상위에 있는 신뢰 기관
중간 CA (Intermediate Certificate Authority)
- 루트 인증서의 손상은
루트 인증서부터 신뢰를 얻는 모든 인증서가 재발급되어야 하는 상황이 생기므로
중간 인증서를 사용하여 최종 사용자에게 서버 인증서를 발급하는 방식으로 보안 수준을 높인다.
1. 인증서 유형 및 서명 관계
1) 루트 인증서 (Root Certificate)
2) 중간 인증서 (Intermediate Certificate)
3) 서버 인증서 (Server Certificate)
example.com)에 대한 신뢰를 보장.example.com 도메인의 소유자임을 증명.2-1. '서버 인증서'는 '중간 인증서'의 비공개키로 서명됨
2-2. '중간 인증서'는 '루트 인증서'의 비공개키로 서명됨
실제 통신 검증 과정
- 서버는 클라이언트에게 서버 인증서와 중간 인증서 전송 (신뢰 체인 확인할 수 있도록)
→ 클라이언트는 중간 인증서의 공개키로 서버 인증서 검증
→ 클라이언트는 루트 인증서의 공개키로 중간 인증서 검증
→ 최종적으로, 루트 인증서가 사전 설치된 인증서인지 확인
