Spring Boot3, Swagger (feat. API 문서 자동 생성)
작성일
삼쩜삼 과제 테스트를 진행하면서 처음 알게 된 Swagger에 대해 정리해보았다. 기존에 API 문서를 생성할 땐 Spring REST Docs를 사용해서 작성한 경험이 있다. Spring REST Docs와 Swagger에 대해 알아보고 Swagger 사용법에 대해서 알아보자.
Spring REST Docs
Spring REST Docs는 테스트 코드를 기반으로 API 문서를 작성할 수 있다. 테스트를 통과한 API만 문서화되기 때문에 안정성을 보장한다. 하지만, 테스트 코드 아래에 이어 붙이는 형식으로 지원하기 때문에 테스트 코드의 양이 많아진다.
Swagger
Swagger는 API 문서 작업을 자동으로 해주는 라이브러리이다. 컨트롤러 등 Production 코드를 바탕으로 API 문서를 작성해준다. 하지만, Production 코드에 Swagger 관련 코드가 들어가기 때문에 코드가 지저분해진다는 단점이 있다.
Swagger는 springdoc와 springfox 라이브러리가 있는데 springfox는 2.7.x 버전까지만 적용할 수 있고 Spring Boot 3 부터는 springdoc을 사용해야 한다고 한다.
dependency 추가
먼저 Swagger를 사용할 때 필요한 Dependency를 추가한다.
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
springdoc:
swagger-ui:
# path: /swagger-ui.html # Swagger API 문서 주소 설정
operationsSorter: method
disable-swagger-default-url: true
display-request-duration: true
show-actuator: true
default-consumes-media-type: application/json
default-produces-media-type: application/json
Swagger 설정 파일
JWT 토큰을 사용하기 때문에 이와 관련된 설정도 추가해주었다.
package com.szs.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@OpenAPIDefinition(info = @Info(title = "API 명세서", version = "v1"))
@RequiredArgsConstructor
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
String jwtSchemeName = "JWT Token";
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT"));
return new OpenAPI()
.components(components)
.addSecurityItem(securityRequirement);
}
}
JWT Token 사용 예시
curl -X 'GET' \
'http://localhost:8080/szs/me' \
-H 'accept: application/json' \
-H 'Authorization: Bearer xxxx' // JWT Token 설정
컨트롤러
package com.szs.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/szs")
public class UserController {
private final UserService userService;
@Tag(name = "회원가입, 로그인")
@Operation(summary = "로그인", description = "로그인을 성공하면 토큰을 반환합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "NOT FOUND",
content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "409", description = "UNAUTHORIZED",
content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR",
content = @Content(schema = @Schema(implementation = Response.class)))
})
@PostMapping("/login")
public Response<UserLoginResponse> login(
@Parameter(description = "로그인 시 필요한 요청 정보", required = true, content = @Content(schema = @Schema(implementation = UserLoginRequest.class)))
@RequestBody UserLoginRequest request) {
String token = userService.login(request.toDto(request));
return Response.success(new UserLoginResponse(token));
}
}
- @Tag: Tag에 설정된 name이 같은 것 끼리 하나의 API 그룹으로 묶는다.
- @Operation: 해당 API가 어떤 리소스를 나타내는지 간략한 설명을 추가할 수 있다.
- @ApiResponses, @ApiResponse: API의 Response 정보를 나타낼 수 있다. @ApiResponses는 여러 Response 들을 묶어 사용할 수 있고, @ApiResponse는 단일 Response에 대한 정보를 나타낸다.
Request Body 정보 명세
package com.szs.controller.request;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "로그인 요청 정보")
public record UserLoginRequest(
@Schema(description = "아이디", example = "hong12")
String userId,
@Schema(description = "패스워드", example = "password1213")
String password) {
}
- @Schema: 클래스 레벨에 사용하면 해당 클래스가 어떤 클래스인지 설명을 작성할 수 있고, 필드에 사용하면 각 필드에 대한 설명, 예시, 필수 값 여부를 추가할 수 있다.
JWT 토큰
JWT 토큰이 필요한 경우 아래 그림과 같이 초록색 버튼을 클릭 후 토큰을 입력한 후 요청을 진행하면 된다.
Swagger 기본 주소
Swagger의 기본 주소는 http://localhost:8080/swagger-ui/index.html 이다.
만약 주소를 변경하고 싶다면 yml 설정 파일에서 변경하면 된다.
springdoc.swagger-ui.path: /swagger-ui.html
마치며
Spring REST Docs와 Swagger를 모두 사용해본 경험으로는 Swagger보단 Spring REST Docs를 사용하는 것이 좋은 것 같다. 일단, 컨트롤러 코드에 Swagger 관련 코드를 사용하는 것이 너무 지저분하다..🥲 Spring REST Docs를 사용하면 테스트 코드를 작성하는 겸.. 사용할 수 있으니 장점이지 않을까 하는 생각을 한다.