본문으로 건너뛰기
중급약 25분

우리 프로젝트에 Swagger 붙이기: 자동 생성 설정, 문서 품질, 운영 노출 차단

Spring Boot·FastAPI·NestJS에서 OpenAPI 문서가 자동 생성되는 원리와 설정을 확인하고, QA 관점에서 문서 품질을 점검하고, 스펙 변화를 CI에서 감지하며, 운영 환경 노출을 막는 데까지 적용한다.

  • #Swagger
  • #OpenAPI
  • #springdoc
  • #FastAPI
  • #NestJS
  • #문서품질

개념

QA가 왜 붙이는 방법까지 알아야 하나

붙이는 일 자체는 대개 백엔드 개발자가 한다. 그런데 QA가 원리를 알면 세 가지가 달라진다. 문서가 부실할 때 "무엇을 어디에 적어달라"고 정확히 요청 할 수 있고, 문서가 없는 팀에 도입을 설득 할 수 있고, 운영 환경에 열려 있는 Swagger 같은 위험을 먼저 발견 할 수 있다. 세 번째는 실제로 보안 지적 사항으로 자주 올라온다.

자동 생성의 원리 — 코드가 곧 문서다

Swagger UI가 읽는 스펙은 사람이 YAML을 손으로 쓰는 것이 아니라 서버 코드에서 생성 되는 것이 일반적이다(코드 우선 방식).

컨트롤러 코드 + 어노테이션/타입    ──▶  /v3/api-docs (OpenAPI JSON)
                                               │
                                               ▼
                                    Swagger UI 화면 (/swagger-ui.html)

여기서 QA가 기억할 원칙 하나가 나온다. 화면이 부실한 것은 도구 탓이 아니라 코드에 정보가 없기 때문이다. "설명을 좀 더 써주세요"는 막연하지만, "이 DTO 필드에 @Schema(description=..., example=...)을 달아주세요"는 바로 처리 가능한 요청이 된다.

반대 방향인 설계 우선(design-first) 도 있다. 스펙 YAML을 먼저 합의하고 그로부터 코드 뼈대를 생성하는 방식으로, 모듈 19(레슨 3)의 Apidog이 이 흐름에 가깝다. 어느 쪽이든 스펙이 하나뿐이어야 한다 는 원칙은 같다.

Spring Boot — springdoc-openapi

Spring Boot 3.x에서는 springdoc-openapi 2.x를 쓴다(구버전 Springfox는 Spring Boot 3과 호환되지 않아 마이그레이션 대상이다).

// build.gradle
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.0'

의존성만 넣으면 기본 주소가 생긴다.

스펙(JSON)   http://localhost:8080/v3/api-docs
화면         http://localhost:8080/swagger-ui.html   (→ /swagger-ui/index.html 로 이동)

레슨 6에서 본 Authorize 버튼은 보안 스킴을 등록해야 생긴다.

@Configuration
public class OpenApiConfig {
  @Bean
  public OpenAPI api() {
    SecurityScheme bearer = new SecurityScheme()
        .type(SecurityScheme.Type.HTTP)
        .scheme("bearer")
        .bearerFormat("JWT");
    return new OpenAPI()
        .info(new Info().title("주문 API").version("v1"))
        .components(new Components().addSecuritySchemes("bearerAuth", bearer))
        .addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
  }
}

엔드포인트·필드 설명은 어노테이션으로 붙인다.

@Operation(summary = "주문 단건 조회", description = "주문 ID로 주문 상세를 조회한다")
@ApiResponses({
  @ApiResponse(responseCode = "200", description = "조회 성공"),
  @ApiResponse(responseCode = "404", description = "존재하지 않는 주문"),
  @ApiResponse(responseCode = "403", description = "다른 사용자의 주문")
})
@GetMapping("/orders/{id}")
public OrderResponse get(@Parameter(description = "주문 ID", example = "1001") @PathVariable Long id) { ... }

QA가 리뷰에서 가장 자주 요청하게 되는 것이 @ApiResponses다. 성공 응답만 적힌 문서는 절반짜리다 — 실패 경로가 문서에 없으면 프런트엔드는 에러 처리를 상상으로 만든다.

FastAPI — 설정이 아예 없다

FastAPI는 타입 힌트와 Pydantic 모델에서 스펙을 만들어내므로 추가 라이브러리가 필요 없다. 기본으로 세 주소가 열린다.

/openapi.json   스펙
/docs           Swagger UI
/redoc          Redoc (읽기 전용 문서)
class OrderResponse(BaseModel):
    id: int
    status: Literal["PAID", "SHIPPED", "CANCELED"]
    total_price: int = Field(..., example=9000, description="쿠폰 적용 후 결제 금액")
 
@app.get("/orders/{order_id}", response_model=OrderResponse, summary="주문 단건 조회")
def get_order(order_id: int): ...

Literal이 스펙의 enum으로 그대로 나간다는 점을 눈여겨보자. 화면의 enum 목록은 곧 상태 전이 테스트의 입력 목록 이다(모듈 3 레슨 3의 상태 전이 기법과 바로 이어진다).

NestJS — 데코레이터와 CLI 플러그인

const config = new DocumentBuilder()
  .setTitle('주문 API')
  .setVersion('v1')
  .addBearerAuth()               // ← Authorize 버튼
  .build();
SwaggerModule.setup('api-docs', app, SwaggerModule.createDocument(app, config));

@ApiProperty({ example: 9000 }) 를 DTO에 붙이면 필드 설명이 채워지고, nest-cli.json에 "plugins": ["@nestjs/swagger"] 를 넣으면 타입에서 상당 부분을 자동으로 뽑아준다.

붙였다고 끝이 아니다 — 문서 품질 점검표

도구를 켠 팀과 문서가 쓸 만한 팀은 다르다. QA가 스펙 리뷰에서 볼 항목은 다음과 같다.

  • 엔드포인트마다 한 줄 설명(summary) 이 있는가 — getOrder1 같은 메서드명이 그대로 노출되고 있지 않은가
  • 요청 필드의 필수 여부 가 표시돼 있는가
  • example 값 이 string, 0 자리표시자가 아니라 현실적인가
  • enum 값이 코드와 일치하는가(빠진 상태값이 없는가)
  • 에러 응답(400/401/403/404/409/500) 이 정의돼 있고 본문 형식이 일관적인가
  • 인증 방식이 정의돼 Authorize 버튼이 동작하는가
  • 서버 목록에 개발·스테이징이 구분돼 있는가
  • 더 이상 쓰지 않는 API가 남아 있지 않은가(@Deprecated 표시)
  • 페이지네이션·정렬 파라미터의 기본값과 최댓값이 적혀 있는가

이 점검표는 모듈 12(레슨 4)의 계약 테스트와 짝이다. 계약 테스트는 "스펙과 실제가 같은가"를 보고, 이 점검표는 "스펙 자체가 쓸 만한가" 를 본다. 스펙이 비어 있으면 계약 테스트도 검증할 것이 없다.

스펙 변화를 CI에서 감지하기

문서가 자동 생성된다는 말은 아무도 모르게 계약이 바뀔 수 있다 는 뜻이기도 하다. 개발자가 필드 하나를 지우면 문서도 조용히 따라 바뀐다.

그래서 스펙 파일을 저장소에 커밋해두고, 빌드 때 생성한 스펙과 비교해 차이를 리뷰에 노출시키는 방식을 쓴다.

# 실행 중인 앱에서 현재 스펙을 받아 저장소 사본과 비교
curl -s http://localhost:8080/v3/api-docs | jq -S . > build/openapi.json
diff <(jq -S . api/openapi.json) build/openapi.json || echo "스펙이 변경됨 — 리뷰 필요"

여기서 한 걸음 더 가면 oasdiff 같은 도구로 호환성을 깨는 변경 (필수 필드 추가, 응답 필드 삭제, enum 값 제거)만 골라 파이프라인을 실패시킬 수 있다. 모듈 14에서 배운 "변경을 자동으로 알아채는 장치"를 API 계약에 적용한 형태다.

운영 환경에는 열어두지 않는다

Swagger UI는 모든 엔드포인트·파라미터·내부 필드명을 한 화면에 정리해주는 도구 다. 공격자에게도 그렇다. 그래서 운영 배포에서는 끄거나 인증 뒤에 두는 것이 기본이다.

# application-prod.yml
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

주의할 점은 UI만 끄고 스펙 경로를 열어두면 의미가 없다 는 것이다. /swagger-ui.html이 404여도 /v3/api-docs가 열려 있으면 정보는 그대로 새어 나간다. QA 점검 항목으로 넣어둘 만하다 — 운영 도메인에서 스펙 주소를 직접 호출해 보는 것까지가 한 세트다.

FastAPI는 FastAPI(docs_url=None, redoc_url=None, openapi_url=None) 로 같은 일을 한다. 완전히 닫는 대신 사내망·VPN·관리자 인증 뒤에 두는 절충도 흔하다.

Swagger UI 말고 다른 선택지

같은 스펙 파일을 읽는 다른 화면들도 있다. 팀이 이미 다른 것을 쓰고 있어도 당황할 필요가 없다 — 원본은 똑같은 OpenAPI 스펙이다.

도구성격언제
Swagger UI실행 가능(Try it out), 가장 널리 쓰임표준. QA가 직접 두드려야 할 때
Redoc읽기 전용, 3단 레이아웃·검색이 강함외부 공개 레퍼런스 문서
Scalar최신 UI + 내장 API 클라이언트새 프로젝트에서 점점 기본값이 되는 추세
RapiDoc가벼운 웹 컴포넌트사내 포털에 끼워 넣을 때

OpenAPI 3.1 을 쓰는 프로젝트라면 화면 지원 여부를 먼저 확인한다. Swagger UI는 3.1에서 일부 표현이 어색하게 나오는 경우가 보고돼 있어, 3.1 기반 신규 프로젝트가 Scalar·Redoc을 고르는 이유가 되기도 한다.

실무에서 왜 필요한가

문서가 없는 팀에서 QA가 겪는 손실은 눈에 잘 안 띄지만 크다. API 하나 확인할 때마다 개발자에게 물어보고, 답을 기다리고, 그 답이 슬랙 어딘가에 묻힌다. Swagger를 붙이는 일은 그 왕복을 없애는 인프라 작업 이고, QA는 그 효용을 가장 크게 체감하는 당사자이므로 도입을 제안하기에 가장 좋은 위치에 있다.

또한 이 레슨의 점검표는 QA가 코드를 못 읽어도 개발 품질에 개입할 수 있는 통로 다. "에러 응답이 문서에 없습니다", "enum에 EXPIRED가 빠졌습니다" 같은 지적은 구현 이전에 나올 수 있고, 그 시점이 가장 싸다. 운영 노출 점검은 그 자체로 보안 점검 항목이라 모듈 23(보안 테스트 도구)의 정보 노출 점검과도 이어진다.

실습 과제

과제 1 — 스펙 원문 읽기 (10분)

레슨 6에서 쓴 공개 스펙(https://petstore3.swagger.io/api/v3/openapi.json) 을 브라우저로 직접 연다. 화면이 아니라 JSON 원문 에서 다음을 찾아 적는다.

  1. components.securitySchemes에 정의된 인증 방식과 그 종류
  2. 아무 엔드포인트 하나의 responses에 정의된 상태 코드 전부
  3. required 배열에 들어 있는 필드와, 화면에서 * 로 표시되던 필드가 일치하는지
해설 보기

핵심은 화면에서 본 것이 전부 이 JSON에서 온다 는 사실을 눈으로 확인하는 것이다. 이 대응 관계가 잡히면 "문서에 이게 없어요"라는 말을 "스펙의 responses에 4xx가 정의돼 있지 않습니다"로 바꿔 말할 수 있게 되고, 그때부터 개발자와의 대화가 짧아진다.

Petstore 스펙에서는 에러 응답이 상당히 빈약하게 정의돼 있는 것을 볼 수 있는데, 이 또한 좋은 교보재다 — 공식 예제조차 그렇다.

과제 2 — 문서 품질 점검표 적용하기 (10분)

본문의 9개 점검 항목을 그대로 가져와, 실제 API 문서(회사 것이 없다면 Petstore) 하나에 적용해 표로 채운다. 미달 항목 중 가장 먼저 고쳐야 할 것 하나 를 고르고, 개발자에게 보낼 요청 문장을 한 줄로 쓴다 (어떤 파일·어떤 어노테이션인지까지 특정할 것).

과제 3 — 운영 노출 점검 (5분)

자신이 아는 서비스(또는 사내 스테이징)의 도메인에 대해 /swagger-ui.html, /v3/api-docs, /docs, /openapi.json, /api-docs 를 차례로 열어본다. 반드시 테스트 권한이 있는 대상에만 수행한다. 열려 있다면 무엇이 노출되는지 3줄로 정리하고, 보고할 때 어떤 근거와 조치를 함께 적을지 구상한다.

자가 체크리스트

  • 코드에서 OpenAPI 스펙이 생성되고 화면이 그려지는 경로를 설명할 수 있다
  • springdoc·FastAPI·NestJS의 기본 문서 주소를 각각 안다
  • Authorize 버튼이 보이려면 무엇이 설정돼야 하는지 안다
  • 문서 품질 점검표 9개 항목으로 실제 문서를 리뷰할 수 있다
  • 스펙 변경을 CI에서 감지하는 방법을 한 가지 이상 말할 수 있다
  • 운영 환경에서 UI만 끄는 것이 왜 불충분한지 설명할 수 있다

흔한 실수

  • UI만 끄고 /v3/api-docs를 열어둔다. 스펙 경로가 살아 있으면 엔드포인트 목록은 그대로 노출된다. 둘 다 막혔는지 확인한다.
  • 문서를 손으로 따로 관리한다. 위키에 API 표를 복사해두면 코드와 어긋나는 순간 진실의 출처가 둘이 된다 — 문서 문제는 다시 원점이다.
  • 성공 응답만 문서화한다. 실패 경로가 없는 문서는 프런트엔드가 에러 처리를 추측으로 만들게 하고, 그 추측이 곧 결함이 된다.
  • example 값을 자리표시자로 방치한다. "string", 0 만 있는 문서는 Try it out을 눌러도 곧장 400이 나서 아무도 쓰지 않게 된다.
  • deprecated API를 목록에 그대로 둔다. 신입이 그 API로 개발을 시작하는 사고가 실제로 난다 — @Deprecated 표시가 문서에 반영되는지 확인한다.
  • 스펙이 조용히 바뀌는 것을 방치한다. 필드 삭제·enum 축소는 곧바로 연동 장애가 된다. 스펙을 커밋하고 diff를 리뷰에 올린다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
YAML — 야믈
들여쓰기로 구조를 나타내는 설정 파일 형식. 사람이 읽기 쉬운 쪽으로 만든 것이다.
CLI — 명령 줄 인터페이스
검은 화면에 글자로 명령을 적어 넣는 방식. 버튼은 없고 줄마다 명령을 쓴다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
CI — 지속적 통합
코드를 올릴 때마다 자동으로 합쳐서 빌드하고 시험까지 돌리는 방식.
VPN — 가상 사설망
공용 인터넷을 지나면서도 사내망에 직접 연결된 것처럼 쓰게 해 주는 장치.
OpenAPI — 오픈에이피아이 명세
창구가 어떤 주소로 무엇을 받고 무엇을 돌려주는지 정해진 형식으로 적어 둔 문서.
JSON — 제이슨
프로그램끼리 값을 주고받을 때 쓰는 글자 형식. 괄호와 따옴표로 이름과 값을 짝지어 적는다.

용어집 전체 보기