티스토리 뷰
개요
백엔드 서버에는 다양한 미들웨어가 기본적으로 들어가고 또한 커스텀한 미들웨어를 만들어 추가하기도 한다.
Echo 서버에 중요 미들웨어를 추가해보자.
링크
- GitHub 브랜치: https://github.com/nicewook/gocore/tree/5_middleware
- 블로그 링크
미들웨어 의존성 추가
middlewares 패키지의 RegisterMiddlewares 를 주입하였다.
func main() {
app := fx.New(
fx.Provide(
NewConfig,
NewDB,
echo.New,
),
fx.Provide(
repository.NewUserRepository,
repository.NewProductRepository,
repository.NewOrderRepository,
),
fx.Provide(
usecase.NewUserUseCase,
usecase.NewProductUseCase,
usecase.NewOrderUseCase,
),
fx.Invoke(
middlewares.RegisterMiddlewares,
handler.NewUserHandler,
handler.NewProductHandler,
handler.NewOrderHandler,
),
fx.Invoke(StartServer),
)
app.Run()
}
미들웨어 추가
internal/middlewares/middlewares.go 파일의 RegisterMiddlewares 에서 미들웨어 설정의 전체 코드를 확인 할 수 있다. 지금부터 하나씩 챙겨보자.
미들웨어 제공: https://echo.labstack.com/docs/category/middleware
Remove Trailing Slash
// ✅ Trailing Slash 제거 및 301 리디렉트 설정
e.Pre(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently, // 301 리디렉트
}))
URL 끝의 슬래시(/)를 자동 제거하고 영구 리디렉트 한다(예시: /about/ → /about). 이처럼 자동 제거를 하면 URL 구조의 일관성을 가지며, 검색엔진이 정규 URL로 인식하여 SEO에 유리하다. 참고로 자동 추가를 해주는 미들웨어도 있으며 이는 정적웹사이트에서는 유리하다 한다.
RequestID
// ✅ RequestID: 각 요청에 고유한 ID 부여 (추적 및 디버깅 목적)
e.Use(middleware.RequestID())
각 요청에 고유한 ID를 부여하여 요청을 추적하거나 디버깅을 하는 것이 목적이다. 이 미들웨어를 사용하면, Echo는 요청에 X-Request-Id 헤더가 있으면 해당 값을 그대로 사용하지만, 없으면 새로이 생성하며, 응답 헤더에 X-Request-Id 값을 추가한다. X-Request-Id 는 HTTP 표준 헤더는 아니지만 관행적으로(de facto) 사용한다.
값을 다시 추출해내는 것은 다음과 같이 간단하다.
e.GET("/example", func(c echo.Context) error {
requestID := c.Response().Header().Get(echo.HeaderXRequestID) // 응답 헤더에서 가져옴
return c.String(http.StatusOK, "Request ID: "+requestID)
})
Logger
// ✅ Logger: 요청 및 응답 로깅 설정
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `[${time_rfc3339}] ${method} ${uri} ${status} request_id=${id}\\n`,
}))
각 요청과 그 응답에 대한 정보를 로그로 남겨주는 미들웨어이다. 특정 경로는 로깅에서 제외할 수도 있고(예, 헬스체크), 콘솔이 아닌 파일로 로깅하도록 설정할 수도 있다. 위 포맷으로 출력된다면 다음 예시와 같을 것이다.
[2025-02-18T15:04:05+09:00] GET /api/users?id=123 200 request_id=abc123xyz
실제 사용할 수 있는 정보는 다음과 같이 훨씬 많다. latency는 중요 모니터링 지표가 될 수 있다.
변수 설명
${time_rfc3339} | 요청 시각 (RFC3339 포맷, 예: 2025-02-18T15:04:05Z07:00) |
${method} | 요청 메서드 (예: GET, POST) |
${uri} | 요청된 경로 및 쿼리 (예: /api/users?id=123) |
${status} | 응답 상태 코드 (예: 200, 404) |
${id} | RequestID (Echo의 RequestID 미들웨어가 필요) |
${latency} | 요청-응답 소요 시간 (예: 1.245ms) |
${remote_ip} | 클라이언트 IP 주소 |
${host} | 요청 호스트 (예: example.com) |
${protocol} | 프로토콜 (예: HTTP/1.1) |
${error} | 발생한 에러 메시지 |
${bytes_in} | 요청 바이트 크기 |
${bytes_out} | 응답 바이트 크기 |
Recover
// ✅ Recover: 패닉 발생 시 복구 및 로그 출력
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
StackSize: 1 << 10, // 스택 크기: 1KB
LogLevel: log.ERROR,
}))
Recover 미들웨어는 프로덕션 환경에서 패닉 발생 시 서버 크래시를 방지하고 자동으로 복구해 서비스가 중단되지 않도록 하는 것이 일차 목적이다. 또한, 패닉 원인과 스택 트레이스를 로그로 남겨 디버깅에 도움을 주며, 정상적인 HTTP 응답(500 에러)를 클라이언트에게 반환해준다.
프로덕션에서는 로그만 남기고 클라이언트에게는 에러의 세부정보를 감추도록 설정하는 것을 추천한다.
Gzip
// ✅ Gzip: 응답 압축 (성능 최적화)
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: 5, // 압축 레벨 (1-9)
MinLength: 128, // 최소 압축 크기 (128바이트 이상만 압축)
Skipper: func(c echo.Context) bool {
return strings.Contains(c.Path(), "metrics") // 특정 경로는 압축 제외
},
}))
클라이언트가 Accept-Encoding: gzip을 요청 헤더에 넣어주면, 서버는 Gzip 방식으로 응답 본문(body)을 압축해서 전송한다. 브라우저는 기본적으로 이 헤더를 포함해 요청한다. 응답 데이터 크기가 줄어들어 네트워크 비용을 절감하고 전송속도도 향상된다.
위 설정은 API 서버의 설정으로 무난한 압축율인 5로 하였고, 보통 응답이 크지 않기에 압축을 할 기준이 되는 크기도 작게 설정하였다. 프로메테우스가 메트릭을 수집하는 metrics와 같이 특정 경로가 잦은 빈도로 요청을 하는 경우는 압축과 해제 작업 자체가 성능을 저하시킬 수 있으니 이처럼 압축 제외를 해두는 것이 낫다.
Body Dump
// ✅ BodyDump: 요청/응답 본문 로깅 (대형 요청 감지)
e.Use(middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) {
// 대형 요청/응답 감지 (1MB 초과 시 경고)
if len(reqBody) > 1024*1024 {
e.Logger.Warnf("Large request body: %d bytes for %s %s",
len(reqBody), c.Request().Method, c.Path())
}
if len(resBody) > 1024*1024 {
e.Logger.Warnf("Large response body: %d bytes for %s %s",
len(resBody), c.Request().Method, c.Path())
}
// 민감 경로는 본문 로깅 제외
sensitivePaths := []string{"/login", "/register"} // 예시
for _, path := range sensitivePaths {
if c.Path() == path {
e.Logger.Infof("%s %s [Sensitive path, body logging skipped]",
c.Request().Method, c.Path())
return
}
}
// 디버그 모드에서만 본문 로깅 (1KB로 출력 제한)
if c.Echo().Logger.Level() == log.DEBUG {
// 클로저: 본문 길이 제한 함수
limitBody := func(body []byte, max int) string {
if len(body) > max {
return string(body[:max]) + " [TRUNCATED]"
}
return string(body)
}
// 클로저: Content-Type 검사 함수. 텍스트인 경우만 로깅하기 위함
isTextContent := func(contentType string) bool {
return strings.HasPrefix(contentType, "application/json") ||
strings.HasPrefix(contentType, "text/") ||
strings.HasPrefix(contentType, "application/xml") ||
strings.HasPrefix(contentType, "application/x-www-form-urlencoded")
}
// Content-Type 가져오기 (요청 및 응답)
reqContentType := c.Request().Header.Get(echo.HeaderContentType)
resContentType := c.Response().Writer.Header().Get(echo.HeaderContentType)
// Content-Type이 텍스트일 때만 로깅
if isTextContent(reqContentType) && isTextContent(resContentType) {
e.Logger.Debugf("Request: %s", limitBody(reqBody, 1000))
e.Logger.Debugf("Response: %s", limitBody(resBody, 1000))
} else {
e.Logger.Debugf("Request and Response are non-text content. Skipping log.")
}
}
}))
지나치게 큰 요청이나 응답에 대한 로깅을 남겨두어 탐지, 디버깅, 모니터링을 하기 위한 미들웨어이다. 대형 요청/응답을 감지시에 요청 경로와 사이즈를 로그로 남기도록 하였고, 민감한 경로가 아니고 디버그 모드일 경우에는 텍스트인 경우에 한정된 크기만큼만 본문을 남기도록 하였다.
BodyLimit
// ✅ BodyLimit: 요청 크기 제한 (2MB)
e.Use(middleware.BodyLimit("2M"))
앞의 BodyDump와 함께 적용한다면 1M 이상의 요청은 경고 로그를 남기고, 2M 이상의 요청은 허용하지 않고 413 - Request Entity Too Large응답을 보낸다.
Timeout
// ✅ 핸들러 실행 시간 제한 (서버 비즈니스 로직 보호)
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
Timeout: 30 * time.Second, // 핸들러 내부 실행 시간
}))
// ✅ 서버 자체 타임아웃 설정
e.Server.ReadTimeout = 10 * time.Second // 요청 읽기 타임아웃
e.Server.WriteTimeout = 40 * time.Second // 응답 쓰기 타임아웃 (Handler Timeout보다 길게)
e.Server.IdleTimeout = 120 * time.Second // 유휴 연결 타임아웃 (Keep-Alive)
핸들러 실행 시간 제한
middleware.TimeoutWithConfig 미들웨어는 요청을 받은 모든 핸들러에 타임아웃을 걸어주는 역할을 한다. 대부분의 echo 미들웨어와 마찬가지로 Skipper 필드가 있는데 이 적용을 받지 않는 경우를 명시해준다. 예를 들어 특히나 오래걸리는 작업을 하는 경우라면 예외를 두는 것이다.
- 목적: 서버의 비즈니스 로직의 실행시간 제한
- 측정 구간: 요청 수신 완료(ReadTimeout 종료) 후 → 핸들러(ServeHTTP) 실행 완료 시점까지
- 타임아웃 대상: 비즈니스 로직 (DB 조회, API 호출, 파일 처리 등), 핸들러 내부 코드 실행 시간
- 타임아웃 발생시
- 클라이언트는 504 Gateway Timeout 상태 코드 수신
- 서버는 핸들러 실행 중단 및 context.DeadlineExceeded 발생
- 비유: 주방에서 주문을 받았지만 30초 이내에 요리를 만들지 못하면 타임아웃 발생
서버 자체 타임아웃 설정
핸들러 실행 시간에 비해 좀더 하위 개념이라 볼 수 있다. 내용물을 모른 채 주고 받는 것의 시간제한을 거는 것이다.
ReadTimeout
- 목적: 요청이 너무 느린 클라이언트의 차단
- 측정 구간: 클라이언트와 연결(Accept) 후 → 요청(헤더 + 바디) 수신 완료 시점까지
- 타임아웃 대상: 헤더 전송 지연, 바디 전송 지연 (POST, PUT 등)
- ReadTimeout 초과 시:
- 클라이언트는 408 Request Timeout 상태 코드 수신
- 서버는 핸들러(ServeHTTP)에 진입조차 하지 않음
- 비유: 고객이 전화로 요리를 주문하겠다고 했는데, 10초 동안이나 아무 말도 안해서 식당주인이 전화를 끊음
WriteTimeout
- 목적: 응답 전송 지연 방지 (Handler Timeout보다 길게)
- 중요: 핸들러 실행 시간 제한은 이 안에 포함이 된다. 핸들러 실행 시간 제한은 타임아웃이 핸들러 실행이 너무 오래 걸려서인지 응답 전송이 너무 오래 걸려서 인지를 좀더 세분하기 위해 생겨난 개념이다. 따라서 WriteTimeout 은 핸들러 실행 시간 제한 보다 항상 길어야 한다.
- 측정 구간: 요청 수신 완료 후 → 응답(헤더 + 바디) 전송 완료 시점까지(엄밀히 말하면 조금 복잡해진다)
- 타임아웃 대상:
- 응답 헤더 전송 지연
- 응답 바디 전송 지연 (대용량 파일, 스트림 전송 등)
- HTTPS의 경우 TLS 핸드셰이크 포함
- WriteTimeout 초과 시:
- 클라이언트는 “연결이 닫혔습니다(Connection reset)” 오류 수신
- 504는 전송되지 않음, 클라이언트는 단순 연결 끊김으로 인식
- 서버는 응답을 중단하고 연결을 강제 종료
- 클라이언트는 “연결이 닫혔습니다(Connection reset)” 오류 수신
- 비유: 배달 중이었는데 40초 넘게 걸리자 배달원이 음식을 들고 도중에 그냥 돌아감.
IdleTimeout
- 목적: Keep-Alive 연결 관리 및 리소스 절약
- 측정 구간: 클라이언트에게 응답 완료 후(WriteTimeout 종료) → 다음 요청을 기다리는 시간
- 타임아웃 대상:
- HTTP/1.1 Keep-Alive 연결 유지 시간
- 클라이언트가 연결은 열어두고 요청을 하지 않는 상태
- IdleTimeout 초과 시:
- 서버는 연결을 종료 (Connection: close)
- 클라이언트는 다음 요청 시 새 연결 생성
- 비유: 카페에서 커피 다 마시고 추가 주문없이 2시간 동안 자리만 차지하면 직원이 나가달라고 요청한다
보안 관련 미들웨어 추가
Rate Limiter
// ✅ Rate Limiter: 요청 속도 제한 (DDoS 방지)
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Store: middleware.NewRateLimiterMemoryStoreWithConfig(
middleware.RateLimiterMemoryStoreConfig{
Rate: rate.Limit(10), // 초당 10회 요청
Burst: 30, // 버스트 최대 30회
ExpiresIn: 1 * time.Minute, // 1분 주기로 리셋
},
),
// 클라이언트 식별: IP
IdentifierExtractor: func(c echo.Context) (string, error) {
ip := c.RealIP()
if ip == "" {
e.Logger.Warn("RateLimiter: Failed to extract client IP")
return "", errors.New("unable to determine client IP")
}
return "ip:" + ip, nil
},
// IdentifierExtractor 실패 시 처리
ErrorHandler: func(c echo.Context, err error) error {
e.Logger.Errorf("RateLimiter: Identifier extraction failed: %v", err)
return c.JSON(http.StatusForbidden, map[string]string{
"error": "Invalid client identifier",
})
},
// 요청 초과 시 처리
DenyHandler: func(c echo.Context, identifier string, err error) error {
e.Logger.Warnf("RateLimiter: Rate limit exceeded for identifier: %s", identifier)
return c.JSON(http.StatusTooManyRequests, map[string]string{
"error": "Rate limit exceeded",
})
},
}))
한 줄로 미들웨어를 설정할 수도 있지만 개념이해를 돕기 위해 상세한 설정을 덧붙였다.
Store
헷갈릴 수 있기에 설명을 덧붙여본다.
- 처음에는 버스트를 허용한다. 여기서는 초당 11-30개까지의 버스트 모드 요청까지도 받아줄 수 있는 상태이다.
- 그러다가 한 번이라도 버스트 모드로 요청이 들어오면 그 다음부터 ExpiresIn 설정이 만료되는, 여기서는 1분이 지날때까지는 버스트 모드 요청을 받지 않는다.
- 버스트 모드가 제한되는 동안은 초당 10개의 요청만 받고 그것을 초과하는 경우의 요청은 거부한다.
Store: middleware.NewRateLimiterMemoryStoreWithConfig(
middleware.RateLimiterMemoryStoreConfig{
Rate: rate.Limit(10), // 초당 10회 요청
Burst: 30, // 버스트 최대 30회
ExpiresIn: 1 * time.Minute, // 1분 주기로 버스트 모드 리셋
},
),
IdentifierExtractor
- 같은 클라이언트인지를 구분하는 기준이다.
- 여기서는 클라이언트 IP 이지만, 구현에 따라 API키 등을 사용할 수도 있겠다.
ErrorHandler
- IdentifierExtractor 에서 에러가 나는 경우의 응답을 정의한다. 여기서는 http.*StatusForbidden*
DenyHandler
- Rate 또는 Burst 설정의 제한을 넘어서는 요청에 대한 응답을 정의한다. 여기서는 http.StatusTooManyRequests
CORS(Cross-Origin Resource Sharing)
// ✅ CORS: Cross-Origin Resource Sharing 설정
const HeaderXCSRFToken = "X-CSRF-Token"
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: cfg.Secure.CORSAllowOrigins, // 각 환경의 설정에 허용하는 도메인을 설정해둔다
AllowMethods: []string{ // 허용할 HTTP 메서드
echo.GET,
echo.POST,
echo.PUT,
echo.DELETE,
echo.PATCH,
echo.OPTIONS,
},
AllowHeaders: []string{ // 허용할 요청 헤더
echo.HeaderOrigin,
echo.HeaderContentType,
echo.HeaderAccept,
echo.HeaderAuthorization,
echo.HeaderXCSRFToken,
},
AllowCredentials: true,
ExposeHeaders: []string{echo.HeaderXRequestID},
MaxAge: 86400,
}))
CORS는 웹 브라우저가 다른 출처(origin)에서 요청을 보낼 때 보안 정책을 정의하는 규칙이다. 브라우저는 기본적으로 다른 도메인, 프로토콜, 포트의 요청을 차단하며, CORS 설정에 명시한 출처와 요청 방법만 허용한다.
- AllowOrigins: config.{env}.yaml 파일에 허용하는 출처 항목을 추가하였다. 각 환경별로 설정이 가능하다.
- // config/config.dev.yaml // 전략 secure: cors_allow_origins: - "<http://your.frontend.com>" - "" jwt_secret: "your_jwt_secret"
- AllowMethods: 허용하는 HTTP 메서드. OPTIONS는 Preflight 요청인데, 브라우저가 보안 목적으로 실제 요청 전 OPTIONS 메서드로 서버에 요청 허용 여부를 확인할 때 사용한다.
- AllowHeaders: 클라이언트가 요청에 포함할 수 있는 헤더의 목록이
- AllowCredentials: 브라우저가 요청 시 쿠키, 인증 헤더, 세션 정보의 포함을 허용하는 것
- AllowOrigins에 와일드카드(*)를 포함하면 사용할 수 없다.
- JWT를 쿠키(HTTPOnly)에 저장한다면 true여야 한다.
- ExposeHeaders: 브라우저가 기본적으로 클라이언트에 노출해주는 헤더 이외에 노출을 허용할 헤더.
- echo.HeaderXRequestID(=X-Request-Id)는 클라이언트에서 요청과 관련한 추적, 디버깅에 유용하다.
- MaxAge: echo.OPTIONS 로 얻은 정보를 캐싱해서 재요청하지 않아도 되는 시간
CSRF**(Cross-Site Request Forgery)**
// ✅ CSRF: Cross-Site Request Forgery 방어
if cfg.App.Env != "dev" {
// CSRF token route handler
e.GET("/csrf-token", func(c echo.Context) error {
token := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
return c.JSON(http.StatusOK, map[string]string{
"csrf_token": token,
})
})
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:" + echo.HeaderXCSRFToken,
CookieSecure: false, // HTTPS에서만 쿠키 전송
CookiePath: "/", // 이 설정 추가
CookieName: "_csrf", // 이 설정 추가
CookieHTTPOnly: true, // JavaScript 접근 금지
CookieSameSite: http.SameSiteLaxMode, // 동일 출처 외 요청 차단
}))
}
CSRF(크로스 사이트 요청 위조)는 사용자의 인증된 세션을 악용해 공격자가 원치 않는 요청을 보내는 공격이다. 로그인된 사용자가 원치 않는 요청을 보내게 유도하여 데이터 변경, 결제 요청 등의 피해를 유발할 수 있다. 개발 중 테스트에 번거로울 수 있으니 dev 환경에 적용되지 않도록 해두었다.
클라이언트에서는
- 최초 접근시 한 번은 GET /csrf-token 요청을 통해 csrf-token을 받아야 한다. 또는 모든 요청에 대해 csrf-token 존재여부를 확인 후 없다면 요청해서 받아오도록 해도 된다.
- 이후에는 요청에 X-CSRF-Token 헤더에 csrf-token을 넣어 요청해야 한다. CSRF 공격은 상태 변경 오쳥에 사용되기에 POST, PUT, DELETE 요청에만 넣어주면 된다.
서버의 미들웨어에서는
- TokenLookup: 서버는 클라이언트 요청의 X-CSRF-Token 헤더에서 CSRF 토큰을 찾는다.
- 이것이 쿠키의 값과 일치하는지를 확인하는 것는 것이다.
- CookieSecure: HTTPS 연결에만 쿠키를 전송한다.
- CookieHTTPOnly: 자바스크립트 접근을 막는 것으로 XSS 공격을 방지한다.
- CookieSameSite: SameSite 설정이다. Strict, Lax, None을 설정할 수 있다.
테스트 코드
인텔리제이에서 간단한 테스트 코드를 짜보았다. 테스트를 위해서는 dev 환경에서는 제외하는 코드를 잠시 제거하자.
- CSRF 토큰 받기: 토큰을 받으면 인텔리제이는 이를 csrf_token 에 저장한다. 쿠키는 인텔리제이가 보관하고 이어지는 요청에 함께 보낸다.
- POST 요청: 헤더에 csrf_token을 함께 넣어서 보내면 정상동작한다.
- POST 요청: 헤더에 csrf_token을 넣지 않으면 CSRF 미들웨어에서 에러가 발생한다.
- GET 요청: 헤더에 csrf_token을 넣지 않아도 에러가 발생하지 않는다. POST, PUT, DELETE 메서드에 대해서만 csrf_token을 확인한다.
### CSRF 토큰 받기
GET <http://localhost:8080/csrf-token>
> {%
client.global.set("csrf_token", response.body.csrf_token);
%}
### POST 요청 테스트 - csrf_token이 필요하다.
POST <http://localhost:8080/users>
Content-Type: application/json
X-CSRF-Token: {{csrf_token}}
{
"name": "Hyunseok Jeong 4",
"email": "hyunseok.jeong4@gmail.com"
}
### POST 요청 테스트 - csrf_token이 없으면 에러가 난다.
POST <http://localhost:8080/users>
Content-Type: application/json
{
"name": "Hyunseok Jeong 4",
"email": "hyunseok.jeong4@gmail.com"
}
### GET 요청 테스트 - csrf_token이 필요없다.
GET <http://localhost:8080/users>
Accept: application/json
Secure Headers
**// ✅** Secure Headers: **다양한 보안 헤더 설정**
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "DENY",
// HSTS
HSTSMaxAge: 15768000, // 6개월
HSTSPreloadEnabled: true,
HSTSExcludeSubdomains: false, // 서브도메인까지 HTTPS 강제 적용
// Content Security Policy (CSP)
ContentSecurityPolicy: "default-src 'self'; frame-ancestors 'none'; form-action 'self';" +
"permissions-policy: camera=(), microphone=(), geolocation=();",
// CSP 테스트용 (필요 시)
CSPReportOnly: false,
// Referrer-Policy 강화
ReferrerPolicy: "no-referrer",
}))
각각에 대해 간략히 설명을 남긴다. 실무에 적용할때는 하나하나에 대해 간략한 개념만 잡고 적용한 다음 개발중 개선이나 설정의 변경이 필요한 부분을 만나면 그때 좀더 주제를 심화하여 이해한 다음 조정을 하기를 권한다.
- XSSProtection: "1; mode=block": XSS 공격 감지 시 페이지 로딩을 차단하여 악성 스크립트 실행 방지.
- ContentTypeNosniff: "nosniff": MIME 스니핑 공격을 막아 브라우저가 Content-Type을 강제 변경하지 못하도록 설정.
- XFrameOptions: "DENY": 클릭재킹 공격 방지를 위해 다른 사이트에서 <iframe>으로 내 웹사이트를 포함하지 못하도록 차단.
- HSTSMaxAge: 15768000 (6개월): HTTP → HTTPS 강제 적용, 사용자가 HTTP로 접근해도 자동으로 HTTPS로 리디렉션.
- HSTSPreloadEnabled: true: 크롬 HSTS 프리로드 목록에 도메인을 추가하여 처음부터 HTTPS로만 접속하도록 설정.
- HSTSExcludeSubdomains: false: 서브도메인(sub.example.com)까지 HTTPS 강제를 적용하여 보안 강화.
- ContentSecurityPolicy: "default-src 'self'; frame-ancestors 'none'; form-action 'self'; permissions-policy: camera=(), microphone=(), geolocation=();": 외부 리소스 제한(XSS 방지), 클릭재킹 방어, 카메라·마이크·위치정보 차단.
- CSPReportOnly: false: CSP 정책을 실제 차단(false), 위반 사항만 기록하려면 true로 설정하여 모니터링 가능.
- ReferrerPolicy: "no-referrer": 외부 사이트로 이동 시 내 사이트의 URL 정보(referrer) 유출을 완전히 차단.
마무리
미들웨어는 JWT, logging, 데이터베이스의 트랜잭션 등 추가할 것들이 좀 더 있어 추후에 좀 더 다루겠다. 이번에 함께 다루려 했던 logging 관련은 다음 포스팅에서 이야기하겠다.
'golang' 카테고리의 다른 글
Go 백엔드 5: 의존성 주입 (0) | 2025.02.16 |
---|---|
Go 백엔드 4: 유닛 테스트 (0) | 2025.02.12 |
Go 백엔드 3: 데이터베이스 연결 (0) | 2025.02.09 |
Go 백엔드 2: 설정 (0) | 2025.02.09 |
Go 백엔드 1: 클린 아키텍처 기본 (0) | 2025.02.06 |

- Total
- Today
- Yesterday
- ChatGPT
- websocket
- 클린 애자일
- golang
- 독서
- Gin
- postgres
- 오블완
- 클린 아키텍처
- 엉클 밥
- intellij
- 영화
- 2024년
- agile
- notion
- OpenAI
- bun
- clean agile
- 티스토리챌린지
- 잡학툰
- go
- solid
- 독서후기
- 2023
- API
- Bug
- 노션
- strange
- 인텔리제이
- backend
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |