스웨거를 도입하게 된 이유
기존의 프로젝트에서는 노션으로 API 명세를 하고 있었다.
API 내용을 일일이 쓰는게 꽤 오래걸리긴 하지만, 그만큼 상세하게 설명할 수 있어서 좋았다.
개발 전에는 프론트엔드와 크로스체킹까지 해서 누락되거나 기획상 틀린 정보가 거의 없었다.
문제는 개발을 하는중에 생겼는데,
API 명세서와 다르게 응답값을 설정하는 일이 빈번하게 생겼던 것.
그래서 프론트엔드 팀원께서 많은 불편을 겪었다.
그래서 결국 스웨거를 적용하게 됐다.
해당 프로젝트의 Spring Boot 버전은 3.0이고 springfox가 아닌 springdoc-openapi를 의존한다.
생각보다 springdoc-openapi, 특히 webmvc-ui 버전을 적용하고 정리해놓은 블로그가 많이 없었다.
공식 문서까지 찾아보면서 구현해본 과정을 정리한다.
기본 사용법
전반적인 사용법은 공식 문서 참고.
의존성, yml 설정
build.gradle
dependencies {
//swagger 추가
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
// ...
application.yml
springdoc:
swagger-ui:
path: /api-docs.html # 경로
tagsSorter: alpha # API 알파벳 정렬
operations-sorter: alpha # 엔드포인트 알파벳 정렬
api-docs:
path: /api-docs
show-actuator: true
default-produces-media-type: application/json # 기본 요청, 응답 형태를 JSON으로 설정
default-consumes-media-type: application/json
⚙ 스웨거 설정
우선 Configuration에서 스웨거 Bean을 등록해야 한다.
@OpenAPIDefinition(info = @Info(title = DEFINITION_TITLE, description = DEFINITION_DESCRIPTION, version = DEFINITION_VERSION))
@SecurityScheme(
type = SecuritySchemeType.HTTP,
scheme = SECURITY_SCHEME,
name = SECURITY_SCHEME_NAME,
bearerFormat = SECURITY_SCHEME_BEARER_FORMAT,
description = SECURITY_SCHEME_DESCRIPTION
)
@Configuration
public class SwaggerConfig {
@Bean
public GroupedOpenApi mainAPI() {
return GroupedOpenApi.builder()
.group(DEFINITION_TITLE)
.pathsToMatch(SWAGGER_APPOINTED_PATHS) // 스웨거로 명세화할 엔드포인트들의 URI 경로
.build();
}
}
- @OpenAPIDefinition : 명세서 메타데이터 설정
- @Info : 명세서 설명 정보
- GroupedOpenApi : 스웨거에서는 API 명세서를 여러개로 분리할 수 있게 해준다.
내 경우엔 추후에 다른 버전이 추가될 수 있다고 생각하여 이 객체를 선택했다.
만약 명세서 종류가 한가지만이라면 OpenApi객체를 Bean으로 등록해두면 된다.
아래 사진의 Select a definition 옆에 보이는게 내가 등록해둔 OpenAPI이다.
title = 복쟉복쟉 API 명세서
version = v1
description = 🚀 Realtime ...
자세히 보면 명세서에 들어가는 모든 텍스트를 설정해뒀다.
설정부는 모두 따로 상수로 정의해뒀다.
public class SwaggerConstants {
/**
* swagger
*/
public static final String[] SWAGGER_APPOINTED_PATHS = {
"/**"
};
public static final String DEFINITION_TITLE = "복쟉복쟉 API 명세서";
public static final String DEFINITION_DESCRIPTION = "\uD83D\uDE80 Realtime Congestion Based Location Recommendation Service - 복쟉복쟉 Server의 API 명세서입니다.";
public static final String DEFINITION_VERSION = "v1";
public static final String SECURITY_SCHEME_NAME = "bearer-key";
public static final String SECURITY_SCHEME = "bearer";
public static final String SECURITY_SCHEME_BEARER_FORMAT = "JWT";
public static final String SECURITY_SCHEME_DESCRIPTION = "JWT 토큰 키를 입력해주세요!";
// ...
- @SecurityScheme : Spring Security를 어떤 방식으로 설정했고 동작하는지를 설정한다.
이걸 설정해야 스웨거 페이지에서 토큰 인증이 필요한 API를 사용할 수 있다.
Authorize 버튼을 클릭하면 인증 키 (내 프로젝트의 경우 JWT access token)를 입력할 수 있다.
이렇게 해두면 인증이 필요한 모든 요청에서 헤더에 인증 키를 담아보낸다.
Security 기본 인증 방식인 아이디-비밀번호 인증 방식도 지원한다. 여기선 다루지 않겠음.
Security 설정부의 permitAll URI 추가하기
당연히 스웨거도 서버의 엔드포인트중 하나다.
그런데 Security가 평소에 하던대로 인증을 요구해버리면 우리는 웹 브라우저에서 스웨거 페이지를 볼 수 없다.
// SecurityConfig.java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
// ...
.requestMatchers(GlobalConstants.APPOINTED_URIS).permitAll()
public class GlobalConstants {
public static final String[] APPOINTED_URIS = {
// ...
"/api-docs/**",
"/v1/api-docs",
"/v2/api-docs",
"/docs/**",
"/favicon.ico",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/swagger-ui/#",
"/webjars/**",
"/swagger/**",
"/swagger-ui/**",
"/",
"/csrf",
"/error"
};
⚙ 엔드포인트마다 명세 추가하기
위의 설정만 해두면 스웨거는 정상적으로 동작한다.
그러나... 너무 휑하다.
원활한 협업을 위해서 명세서 정보를 구체적으로 설정해야겠다고 생각했다.
컨트롤러로 가서 엔드포인트마다 설정을 추가해주자.
@RestController
@RequestMapping("/categories")
@RequiredArgsConstructor
@Tag(name = SwaggerConstants.TAG_CATEGORY, description = SwaggerConstants.TAG_CATEGORY_DESCRIPTION)
public class CategoryController {
private final CategoryService categoryService;
@GetMapping
@Operation(summary = SwaggerConstants.CATEGORY_GET_ALL, description = SwaggerConstants.CATEGORY_GET_ALL_DESCRIPTION)
public ResponseEntity<ApiResponse<AllCategoryResponse>> getAllCategory() {
// ...
- @Tag : 해당 API에 대한 메타데이터
- @Operation : 해당 엔드포인트에 대한 메타데이터
상수들은 위의 상수 파일에서 똑같이 설정해뒀다.
결과물.
이제 아까 만든 Authorize 를 동작하게 만드는 일만 남았다.
인증이 필요한 각 엔드포인트마다 @SecurityRequirement를 붙여줘야 한다.
name : 스웨거 설정부에서 만들었던 @SecurityScheme의 name.
동일한 이름으로 설정해줘야 해당 인증 방식으로 알아먹는다.
@SecurityRequirement(name = SwaggerConstants.SECURITY_SCHEME_NAME)
@Tag(name = SwaggerConstants.TAG_COMMENT, description = SwaggerConstants.TAG_COMMENT_DESCRIPTION)
public class CommentController {
// ...
추가적으로, URI가 길어서 불편하다면 리다이렉트해줄 컨트롤러를 만들면 된다.
@Controller
@RequestMapping("/docs")
@Hidden
public class SwaggerController {
@GetMapping
public String redirect() {
return "redirect:/swagger-ui/index.html";
}
}
구현중 트러블 슈팅
⚠️ No operations defined in spec
분명 스웨거 의존성을 추가하고 기본 설정만 해주면, 모든 API 엔드포인트가 나와야 했다.
나오지 않았다.
혹시 몰라서 Controller 메서드에 @Operation
(각 엔드포인트에 대한 이름과 설명 설정을 해주는 어노테이션)까지 추가했는데 스웨거 페이지에서는 아무런 정보가 나오지 않는다.
✅ 해결. URI 패턴이 달랐다.
인터넷에 떠도는 예시들을 가져오다가 생긴 문제.
GroupedOpenApi.pathsToMatch 값을 변경해야 했다.
스웨거에 열어줄 엔드포인트들을 /v1/**
→ /**
로 변경. 이건 실제 API URI를 말한다.
버전 관리를 한다면 모든 API에 /v1
, /v2
, … 식으로 엔드포인트 맨 앞에 붙여주면 되겠다. 그러면 스웨거에서는 버전별로 다른 페이지에서 보여줄 수 있게 이런 기능을 제공하는 듯하다.
'Back-end > Spring Boot' 카테고리의 다른 글
[Spring Boot] 연관관계 생성 메서드 삽질 (0) | 2023.08.23 |
---|---|
[Spring JPA] 실시간으로 적재되는 데이터와 부모 엔티티 묶어서 가져오기 (0) | 2023.08.10 |
[Spring Boot] 예외처리 야무지게 하기 (0) | 2023.08.04 |
[Spring Boot] 서비스에서 로그인된 유저 정보에 대한 의존성 최소화하기 (@AuthenticationPrincipal) + 스프링 계층 구조 지키기 (0) | 2023.07.30 |
[Spring Security] JWT 토큰 인증 이후에 유저 엔티티 객체 불러오기 (Authentication 객체가 담기는 곳) (0) | 2023.07.24 |