[Spring] OpenApI 3.0 Swagger 문서 작성 및 설정 방법
들어가며
Swagger 는 RestDocs 에 비해 단순 적용은 간편하지만 기본적인 설정만으로 사용하기에는 아쉬운 부분이 있다. 이번 글에서는 Open API 3.0 Swagger 를 적용할때 필요한 개념과 조금 더 나은 문서를 만들기 위한 몇가지 노력들을 적어본다.
Spring을 사용한다면 아래 2개의 라이브러리를 사용할 수 있다.
- Spring Fox
- Spring Doc
Spring Fox의 경우 마지막 업데이트일인 2020년 이후 업데이트가 없으므로, Spring Doc을 사용했다.
Springdoc-openapi Modules
SpringDoc OpenAPI는 Spring Boot 뿐만 아니라, WebFlux 도 지원하며 UI 레이어와 REST API 레이어로 구분되어 구성된다.
UI 레이어는 API 문서를 웹 UI로 렌더링하는 역할을 한다.
- springdoc-openapi-starter-webmvc-ui / springdoc-openapi-starter-webflux-ui
- Swagger UI를 제공하는 모듈
- Spring MVC(webmvc-ui)와 Spring WebFlux(webflux-ui)에 따라 다르게 제공
- swagger-ui
- Swagger의 핵심 UI 모듈
- OpenAPI 스펙(JSON 또는 YAML)을 기반으로 웹 UI를 생성
REST API 레이어는 API 문서를 생성하는 역할을 한다.
- springdoc-openapi-starter-webmvc-api / springdoc-openapi-starter-webflux-api
- OpenAPI 스펙을 자동 생성하는 모듈
- spring-webmvc(Spring MVC) 또는 spring-webflux(Spring WebFlux)에 따라 다른 모듈이 사용
- springdoc-openapi-starter-common
- WebMVC와 WebFlux 공통으로 사용하는 OpenAPI 기능을 제공
- swagger-core-jakarta
- Swagger의 코어 기능을 제공
- OpenAPI 문서의 JSON/YAML 스펙을 처리
- spring-boot-autoconfigure
- Spring Boot에서 OpenAPI 관련 설정을 자동으로 적용
따라서 SpringDoc OpenAPI가 애플리케이션의 REST API를 자동으로 분석하고 API의 요청/응답 모델을 기반으로 OpenAPI 문서(JSON, YAML)를 생성 하면 (기본적으로 /v3/api-docs 엔드포인트) Swagger UI가 OpenAPI 문서를 가져와 렌더링하는 구조로 이루어진다.
기본적으로 JSON 과 YAML 형식의 OpenAPI 문서가 모두 자동으로 생성되며, 다음 엔드포인트에서 확인할 수 있다.
- JSON: http://localhost:8080/v3/api-docs
- YAML: http://localhost:8080/v3/api-docs.yaml
설정
build.gradle 에 Springboot 버전별 호환되는 버전을 넣어준다.
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
세부 Property 변경이 필요하다면 application.yml 에서 변경할 수 있다. 공식문서에서 더 자세히 볼 수 있다.
springdoc:
api-docs:
enabled: true # API 문서(`/v3/api-docs`) 활성화 여부 (기본값: true)
path: /v3/api-docs # API 문서의 엔드포인트 변경 (기본값: /v3/api-docs)
groups.enabled: true # 여러 개의 API 그룹을 지원할지 여부
swagger-ui:
enabled: true # Swagger UI 활성화 여부 (기본값: true)
path: /swagger-ui.html # Swagger UI 접속 경로 변경 (기본값: /swagger-ui.html)
operationsSorter: method # API 정렬 기준 (alpha: 알파벳순, method: HTTP 메서드 순)
tagsSorter: alpha # 태그 정렬 방식 (alpha: 알파벳순 정렬)
display-request-duration: true # 요청 실행 시간 표시 여부
doc-expansion: none # Swagger UI에서 API 설명의 기본 펼침 상태 (none: 닫힘, list: 펼침, full: 전체 펼침)
persistAuthorization: true # 페이지 새로고침 후에도 Authorization 헤더 유지 여부
defaultModelsExpandDepth: -1 # 모델 스키마의 기본 펼침 깊이 (-1이면 모델 펼쳐지지 않음)
paths-to-match: # OpenAPI 문서에서 포함할 엔드포인트 패턴
- /api/** # /api로 시작하는 모든 경로 포함
- /admin/** # /admin으로 시작하는 모든 경로 포함
cache:
disabled: true # OpenAPI 문서의 캐싱 비활성화 (기본값: false)
show-actuator: false # Spring Actuator 엔드포인트를 Swagger UI에 표시할지 여부 (기본값: false)
servers:
- url: http://localhost:8080 # 기본 서버 URL 설정
description: "Local 개발 환경"
- url: https://api.example.com # 운영 서버 URL 설정
description: "Production 서버"
자주 사용되는 어노테이션
@Operation
API 엔드포인트의 제목 및 설명을 정의할 수 있다.
@Operation(summary = "유저 정보 조회", description = "유저의 상세 정보를 가져옵니다.")
@Tag
Tag에 설정된 name 이 같은 것끼리 하나의 api 그룹으로 묶인다.
- name : 태그의 이름
- description : 태그에 대한 설명
@Tag(name = "posts", description = "게시물 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
public class PostController {
...
}
@Components
공통적으로 사용할 Schema, Security, Parameter 등을 정의할 수 있다. (주로 Swagger Config 에서 설정한다)
@ApiResponses
여러 응답을 묶어서 표시하기 위해 @ApiResponse 와 함께 사용된다.
@ApiResponse
개별 응답을 정의할 때 사용된다.
- responseCode: HTTP 상태 코드 ex) "200", "400", "500"
- description: 응답에 대한 설명 ex) "조회 성공", "잘못된 요청"
- content: 응답 데이터의 미디어 타입과 스키마를 정의하는 @Content 설정
@Content
API 응답에서 데이터의 형식과 스키마를 정의하는 어노테이션이다.
@ApiResponse 내부에서 사용되며, 응답의 mediaType과 schema 정보를 포함한다.
- mediaType: 응답 데이터의 미디어 타입. ex) "application/json"
- schema: 전체 응답 객체를 정의하는 @Schema
- schemaProperties: JSON의 개별 필드를 @SchemaProperty로 정의
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(
mediaType = "application/json",
schemaProperties = {
@SchemaProperty(name= "result", schema = @Schema(example = "SUCCESS")),
@SchemaProperty(name="data", schema = @Schema(implementation = ProfileResponse.class))
}
)
),
...
})
@SchemaProperty
응답 JSON의 특정 필드에 대한 설명을 추가하는 어노테이션으로 JSON 객체 내부의 하위 필드를 설명할 때 유용하다.
- name: 응답 데이터에서 필드의 이름. ex) "result", "data"
- schema: 필드의 스키마 정의를 위한 @Schema 설정
@Schema
Swagger UI에서 API 요청 및 응답 데이터 모델을 정의하는 데 사용된다. implement 를 이용해 특정 DTO 클래스를 지정해 설정할 수도 있다. @SchemaProperty는 비슷하지만, 적용 대상이 다르다. @SchemaProperty 가 API 응답 내부의 특정 필드에 사용된다면, @Schema 는 DTO 클래스 및 필드에 모두 설정할 수 있다.
- description: 필드 또는 클래스에 대한 설명을 제공
- example: 예제 값을 설정
- required: 필수 필드 여부를 지정
- defaultValue: 기본값을 설정.
- implementation: 특정 DTO 클래스를 지정하여 스키마를 정의
필드에 @Schema 적용
각 필드에 설명을 추가할 수 있다.
public class UserDto {
@Schema(description = "사용자의 이름", example = "홍길동", minLength = 2, maxLength = 10)
private String name;
@Schema(description = "사용자의 이메일 주소", example = "hong@example.com", format = "email")
private String email;
@Schema(description = "사용자의 나이", example = "25", minimum = "18", maximum = "99")
private int age;
}
Enum 타입의 경우, @Schema의 enumAsRef 또는 allowableValues를 사용하여 문서화할 수 있다.
public enum UserRole {
ADMIN, USER, GUEST;
}
public class UserDto {
@Schema(description = "사용자의 역할", example = "USER", allowableValues = {"ADMIN", "USER", "GUEST"})
private UserRole role;
}
implementation을 사용하여 특정 DTO를 참조
다른 DTO를 포함하는 경우 implementation을 사용하여 OpenAPI 문서에서 DTO 간의 관계를 정의할 수 있다.
public class UserResponse {
@Schema(description = "응답 상태", example = "SUCCESS")
private String status;
@Schema(description = "사용자 정보", implementation = UserDto.class)
private UserDto data;
}
@Hidden
특정 API 를 숨길 때 사용한다.
@Parameter(hidden = true)
특정 파라미터를 숨겨야할 때 사용한다. 예를 들자면, HandlerMethodArgumentResolver 를 이용해 파라미터로 받는 객체가 있을 때
기본적으로 swagger 에 표시되므로, 해당 파라미터를 숨기도록 설정할 수 있다.
Swagger Config 설정
OpenAPI 클래스
Swagger Config 를 만들때 OpenApI 클래스에서 제공하는 속성을 변경할 수 있다.
- ex) servers 에 테스트 서버와 실제 운영 서버 주소를 추가하고 API 서버 선택 드롭다운 메뉴에 나타나게 구성
openapi | OpenAPI 명세 버전 (3.0.1 또는 3.1.0) |
info | API 문서의 제목, 설명, 버전 등의 정보를 담는 Info 객체 |
externalDocs | API 문서 외부 링크를 제공하는 ExternalDocumentation 객체 |
servers | API 서버 목록을 정의하는 List<Server> |
security | 보안 요구사항을 정의하는 List<SecurityRequirement> |
tags | API 그룹을 정의하는 List<Tag> |
paths | API 경로와 엔드포인트를 관리하는 Paths 객체 (Map<String, PathItem>) |
components | API의 재사용 가능한 요소(스키마, 보안 스키마, 파라미터 등)를 정의하는 Components 객체 |
extensions | OpenAPI 확장을 위한 Map<String, Object> (커스텀 메타데이터 저장) |
jsonSchemaDialect | OpenAPI 3.1에서 JSON Schema 다이얼렉트를 지정 |
specVersion | OpenAPI 명세의 버전 (SpecVersion.V3_0_1, SpecVersion.V3_1_0) |
webhooks | OpenAPI 3.1에서 Webhook 이벤트를 정의하는 Map<String, PathItem> |
Security Schema 설정 (JWT)
Security Schema를 이용해 Swagger에서 Authorization Header에 JWT 토큰을 입력할 수 있도록 설정할 수 있다.
@Bean
public OpenAPI openAPI() {
SecurityScheme securityScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT");
SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth");
OpenAPI openApi = new OpenAPI()
.components(new Components().addSecuritySchemes("bearerAuth", securityScheme))
.addSecurityItem(securityRequirement)
...
설정하면, 실제 API 요청 시 Authorization 헤더가 자동으로 추가된다. 아래 설정을 추가하면 모든 엔드포인트에 적용된다.
.addSecurityItem(securityRequirement); // 모든 API에 적용
특정 API 에만적용하거나, 적용하지 않을 수도 있다.
// 특정 엔드포인트에서만 적용되도록 설정
@Operation(summary = "내 프로필 조회", security = @SecurityRequirement(name = "Bearer Token"))
// name 속성을 빈 문자열로 설정하면 해당 엔드포인트에서 JWT 인증이 필요하지 않도록 설정
@Operation(summary = "메인페이지", security = @SecurityRequirement(name = ""))
Security Schema 가 적용된 엔드포인트는 API 문서에서 자물쇠 표시가 보이게 된다.
OpenApiCustomizer
OpenApiCustomizer를 사용해 OpenAPI 문서를 코드 레벨에서 동적으로 수정할 수 있다.
예를들어, Tag 들의 순서를 원하는대로 맞추고 싶을 때 아래처럼 적용할 수 있다.
@Bean
public OpenApiCustomizer customOpenAPI() {
List<String> tagOrder = List.of(
"인증", "약관", ... );
return openApi -> openApi.setTags(
openApi.getTags().stream()
.sorted(Comparator.comparingInt(tag -> IntStream.range(0, tagOrder.size())
.filter(i -> tag.getName().contains(tagOrder.get(i)))
.findFirst()
.orElse(tagOrder.size())))
.toList()
);
}
비슷한 방식으로 Schema 정렬도 가능하다.
return openApi -> {
Map<String, Schema> schemas = openApi.getComponents().getSchemas();
openApi.getComponents().setSchemas(new TreeMap<>(schemas));
};
응답 형식 추가
Swagger 는 description 에 html, markdown 을 삽입할 수 있다. 공통응답이나 기재해야할 링크가 있는 경우 활용할 수 있다.
컨트롤러에 붙은 어노테이션을 인터페이스로 분리
문서화을 하다보면 컨트롤러에 Swagger 어노테이션이 많이 붙어있어, 가독성이 떨어지고 보기에도 그닥 좋지 않다. 별도 인터페이스를 만들어 분리해줄 수 있다.
인터페이스의 default 메서드를 이용해 Security 경로 추가
SpringSecurity 에서 UsernamePasswordAuthenticationFilter 를 이용해 로그인을 적용하면 기본적으로 Swagger 에 인식되지 않는다. SecurityFilter 는 MVC 컨트롤러에 도달하기 전에 처리가 이루어지므로, API 응답 표시용으로 default 메서드를 이용해 나타낼 수 있다.
PathItem 을 이용해 OAuth2 소셜 로그인 경로 추가
Spring Security OAuth2 Client 를 이용할 경우, api 주소를 명시적으로 적어줄 수 있다.
API Docs 내보내기
서버를 키지 않았을 때 API 문서를 보거나 전달하기 위해 Swagger 의 API 들을 Export 할 수 있다.
YAML 기본 경로 http://localhost:8080/v3/api-docs.yaml 에서 yaml 을 다운로드받은 뒤 Swagger 에디터에 들어가서 붙여넣는다.
html2 를 선택하고 (html1 보다 UI 가 좋다) 다운받는다.
확인해보면 제대로 변환된 것을 볼 수 있다.