Tech

Springdoc-openapi를 활용한 Spring Boot 기반 API의 문서 자동화

플섬 2022. 9. 19. 18:07

https://springdoc.org/

 

 

 

 

1. Springdoc-openapi

 

프론트엔드와 백엔드의 업무 분리가 명확해지고, REST Api에 의한 서비스 또는 시스템 간의 연계가 증가함에 따라, 자연스레 원활한 협업을 위해 API에 대한 정보의 가공/수정/전파의 중요성이 커지게 되었다.

 

과거 다양한 필요성들이 여러 도구를 만들어 내었듯, 이러한 작업을 보다 쉽고 편리하며 명확하게 하기 위한 여러 시도들이 하나둘 나타나고,  그러한 흐름의 과정에서 Swagger(2011)가 발표되었다. 이후 Swagger는 여러 우여곡절을 거쳐서 2015년 MS, IBM, Google 등이 공동 창립한 OpenAPI Initiative으로 넘어가 2017년에 발표된 OpenAPI Specification 3.0.0의 모태가 되었다.

 

Springdoc-openapi는 OpenAPI Specification을 Spring Boot 프로젝트에서 쉽게 적용하여 활용할 수 있도록 만들어진 Java 라이브러리이다. 여기에는 Swagger-ui를 포함하고 있기 때문에 Spring Boot 프로젝트를 사용하여 개발된 API에 대한 문서 생성을 자동화하는 것은 물론, 테스트 역시 편리하게 진행할 수 있다. 

 

https://springdoc.org

 

 

 

 

 

2. Dependency

 

Spring Boot 프로젝트에 Springdoc-openapi를 적용하기 위해서 필요한 dependency는 아래와 같다.

implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'

 

 

 

 

 

 

3. 예제 프로젝트 구성

 

Springdoc-openapi는 Spring Boot Web 프로젝트에 dependency를 추가하고, application.yml에 간단한 설정을 해주는 것만으로도 기본적인 적용이 가능하다. 

 

build.gradle

plugins {
    id 'org.springframework.boot' version '2.7.3'
    id 'io.spring.dependency-management' version '1.0.13.RELEASE'
    id 'java'
}

group = 'com.tg360tech.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'

    compileOnly 'org.projectlombok:lombok'

    runtimeOnly 'com.h2database:h2'

    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

HomeController

// 전략 //

@RestController
public class HomeController {

    @GetMapping("/")
    public ResponseEntity<String> home() {
        return new ResponseEntity<>("OK", HttpStatus.OK);
    }
}

 

application.yml

server:
  port: 8080

spring:
  application:
    name: SpringDocExample
  profiles:
    active: dev
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password:

springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true
    tagsSorter: alpha
    operations-sorter: alpha
    display-request-duration: true

- springdoc.api-docs.enabled : OpenAPI 인터페이스의 사용 여부를 설정하는데, 결과적으로 Springdoc-openapi의 사용 여부를 결정한다고 볼 수 있다. Springdoc-openapi가 불필요한 실행환경에서는 false를 설정한다.(기본값은 true)

 

- springdoc.swagger-ui.enabled : Swagger-ui의 사용 여부를 결정한다. Swagger-ui가 불필요한 실행환경에서는 false를 설정한다.(기본값은 true)

 

- springdoc.swagger-ui.tagsSorter : Swagger-ui에서 Tag의 정렬 기준을 설정한다. (alpha : 알파벳순)

 

- springdoc.swagger-ui.operations-sorter : Swagger-ui에서 개별 API에 해당하는 Operation의 정렬 기준을 설정한다. (alpha(알파벳) / method(request method))

 

- springdoc.swagger-ui.display-request-duration : Swagger-ui에서 API 실행 시 처리 소요 시간의 표시 여부를 설정한다.

 

 

 

1) api-docs 

예제 프로젝트의 기본 api-docs URL(http://localhost:8080/v3/api-docs)로 api-docs를 조회시 아래와 같은 application/json 데이터를 반환한다.

{
"openapi":"3.0.1",
"info":{"title":"OpenAPI definition",
        "version":"v0"},
"servers":[{"url":"http://localhost:8080","description":"Generated server url"}],
"paths":{"/":{"get":{"tags":["home-controller"],
                     "operationId":"home",
                     "responses":{"200":{"description":"OK",
                                         "content":{"*/*":{"schema":{"type":"string"}}
                                                   }
                                        }
                                 }
                     }
             }
        },
"components":{}
}

Springdoc 1.6.11 버전을 기준으로 OpenAPI Specification v3.0.1 을 지원하고 있는 것을 확인할 수 있는데, 이 데이터는 RESTful 웹서비스를 정의하는 표준 인터페이스 형식이며, 이를 이용해서 Swagger-ui에서 시각화된 화면을 구성하게 된다.

물론 별도의 시각화 솔루션을 이용하여 API를 관리하고자 하는 경우에도, 해당 솔루션이 OpenAPI 표준을 지원하는 경우 이 데이터를 이용하여 연계할 수 있을 것이다.

 

 

 

2) Swagger-ui

예제 프로젝트의 기본 Swagger-ui URL(http://localhost:8080/swagger-ui/index.html)로 접속 시 아래와 같은 UI를 통해서 API 정보를 확인할 수 있다.

 

 

 

 

 

 

4. Config 생성 및 Java 소스에 Annotaion 적용하기

 

API 정보를 상세하게 설정하기 위해서 아래와 같이 Config.java를 작성했다.

 

SpringDocConfig

// 전략 //

@OpenAPIDefinition(
        info = @Info(
                title = "Spring Doc Example API Document",
                description = "API Document",
                version = "v0.1",
                termsOfService = "http://www.tg360tech.com/terms",
                license = @License(
                        name = "Apache License Version 2.0",
                        url = "http://www.apache.org/licenses/LICENSE-2.0"
                ),
                contact = @Contact(
                        name = "dev",
                        email = "dev@tg360tech.com"
                )
        ),
        tags = {
                @Tag(name = "01.Common", description = "공통 기능"),
                @Tag(name = "02.User", description = "사용자 기능"),
                @Tag(name = "03.Undefined", description = "미정의 기능"),
        }
)
@Configuration
public class SpringDocConfig {
}

 

@OpenAPIDefinition : API 전반에 관한 정보를 정의한다.

- info : API 서비스의 기본 정보를 설정한다.

- tags : Swagger-ui에서 API들의 특성과 기능에 따라 배치하고 정리하기 위해서 사용하기 위한 @Tag 정보를 등록한다. 

 

 

HomeController

// 전략 //

@Hidden
@RestController
public class HomeController {

    @GetMapping("/")
    public ResponseEntity<String> home() {
        return new ResponseEntity<>("OK", HttpStatus.OK);
    }
}

 

UserApiController / UserResDto / UserModifyReqDto / UserListReqDto

// 전략 //

@RestController
@RequestMapping("/api/user")
@Tag(name = "02.User")
public class UserApiController {

    @PostMapping("")
    @Operation(summary = "사용자 등록", description = "사용자를 등록한다.", tags = {"02.User", })
    public ResponseEntity<UserResDto> create(@Valid @RequestBody UserModifyReqDto reqDto) {
        UserResDto resDto = new UserResDto(22L, reqDto.getName(), reqDto.getCountry());
        return new ResponseEntity<>(resDto, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    @Operation(summary = "사용자 조회", description = "사용자를 조회한다.")
    public ResponseEntity<UserResDto> user(@PathVariable(name = "id") Long id) {
        UserResDto resDto = new UserResDto(id, "Peter", "US");
        return new ResponseEntity<>(resDto, HttpStatus.OK);
    }

    @PutMapping("/{id}")
    @Operation(summary = "사용자 변경", description = "사용자를 변경한다.")
    public ResponseEntity<UserResDto> modify(@PathVariable(name = "id") Long id,
                                             @Valid @RequestBody UserModifyReqDto reqDto) {
        UserResDto resDto = new UserResDto(id, reqDto.getName(), reqDto.getCountry());
        return new ResponseEntity<>(resDto, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "사용자 삭제", description = "사용자를 삭제한다.")
    public ResponseEntity remove(@PathVariable(name = "id") Long id) {
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping("/list")
    @Operation(summary = "사용자 목록 조회", description = "사용자 목록을 조회한다.")
    public ResponseEntity<List<UserResDto>> user(@Valid @ParameterObject UserListReqDto reqDto) {
        List<UserResDto> list = new ArrayList<>();
        String name = StringUtils.hasLength(reqDto.getName()) ? " " + reqDto.getName() : "";
        String country = StringUtils.hasLength(reqDto.getCountry()) ? reqDto.getCountry() : "US";
        for (long l = 1; l < 11; l++) {
            list.add(new UserResDto(l, "Peter " + l + name, country));
        }
        return new ResponseEntity<>(list, HttpStatus.OK);
    }
}

@Setter
@Getter
@ToString
@AllArgsConstructor
@Schema(title = "사용자 정보")
class UserResDto {
    @Schema(title = "사용자 id", example = "1")
    private Long id;

    @Schema(title = "사용자 이름", example = "홍길동")
    private String name;

    @Schema(title = "국적", example = "KR")
    private String country;
}

@Setter
@Getter
@ToString
@Schema(title = "사용자 정보 등록/변경")
class UserModifyReqDto {
    @NotBlank
    @Schema(description = "사용자 이름", required = true, example = "홍길동")
    private String name;

    @Schema(description = "국적", example = "KR")
    private String country;
}

@Setter
@Getter
@ToString
@Schema(title = "사용자 목록 조회")
class UserListReqDto {
    @Schema(description = "사용자 이름 검색어", example = "홍길동")
    private String name;

    @Schema(description = "국적 검색어", example = "KR")
    private String country;
}

 

CommonController

// 전략 //

@RestController
@RequestMapping("/api/common")
@Tag(name = "01.Common")
public class CommonApiController {

    @GetMapping("/hostname")
    @Operation(summary = "호스트명 조회", description = "실행 서버의 호스트명을 조회한다.",
            responses = {
                    @ApiResponse(responseCode = "200", description = "호스트명 조회에 성공했습니다."),
                    @ApiResponse(responseCode = "500", description = "호스트명 조회 과정에서 오류가 발생했습니다."),
            })
    public ResponseEntity<String> hostname() {

        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return new ResponseEntity<>("ERROR", HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<>(hostname, HttpStatus.OK);
    }

    @GetMapping("/datetime")
    @Operation(summary = "서버일시 조회", description = "실행 서버의 일시를 조회한다.")
    public ResponseEntity<String> datetime() {
        return new ResponseEntity<>(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), HttpStatus.OK);
    }
}

@Hidden : 해당 객체를 표시 대상에서 제외한다. 위와 같이 Class 전체에 적용할 수도 있고, 개별적인 method나 변수에도 적용할 수 있다.

 

@Tag : Controller의 Operation분류에 사용할 Tag를 지정하거나, 신규로 등록한다.

 

@Operation : 개별 API 정보를 설정한다.

- tags : Operation을 배치하려는 Tag의 name을 등록한다. 여러 Tag를 등록하여 배치할 수 있다.

- responses : API에서 가능한 응답들을 정의한다.

 

@Schema : API에서 요청이나 응답에서 사용될 객체 모델의 스키마를 정의한다.

- required : 필수 여부

 

 

위와 같이 OpenAPIDefinition, Tag, Operation 등의 코드를 적용한 후 Swagger-ui를 확인해 보면 다양한 설정 사항의 적용으로 많은 변화가 생겨난 것을 확인할 수 있다.

Tag 설정으로 보기 좋게 정리가 되었다.
개별 API에 대한 Operation 설정 내용과 관련 스키마 정보를 확인할 수 있다.
스키마 목록에서 변수명이나 데이터 타입 등의 확인이 가능하다.

 

 

 

 

 

 

 

5. Spring Security 인증 적용

 

대부분의 API 서비스는 인증과 관련된 코드를 포함하기 마련이기 때문에, Swagger-ui에서 API를 테스트하기 위해서 실행할 때에도 인증 정보를 필요로 하게 된다. 여기에서는 spring-session-data-redis에서 발급한 X-Auth-Token을 이용한 API Key 방식의 인증을 적용해 보기로 한다.

 

build.gradle 에 security, session 관련 dependency 추가

// 전략 //

dependencies {
// 중략 //

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.session:spring-session-data-redis'
    
    testImplementation 'org.springframework.security:spring-security-test'
}

// 후략 //

application.yml

// 전략 //

spring:

// 중략 //

  session:
    store-type: redis
  redis:
    host: localhost
  data:
    redis:
      repositories:
        enabled: false

// 후략 //

 

 

SecurityConfig

// 전략 //

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                   JsonUsernamePasswordAuthenticationFilter authenticationFilter) throws Exception {
        return http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/signIn").permitAll()
                .antMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
                .antMatchers("/api/user/**").hasRole("USER")
                .antMatchers("/api/common/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(restAuthenticationEntryPoint())
                .and()
                .formLogin()
                .disable()
                .logout()
                .logoutUrl("/signOut")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean
    public HttpSessionIdResolver httpSessionStrategy() {
        return HeaderHttpSessionIdResolver.xAuthToken();
    }
    
    //@Bean AuthenticationEntryPoint restAuthenticationEntryPoint

    //@Bean JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter
    
    //@Bean AuthenticationManager authenticationManager

    //@Bean UserDetailsService userDetailsService

    //@Bean PasswordEncoder passwordEncoder
}

 

.antMatcher"/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()

: Swagger-ui에서 사용하기 위한 3가지 경로에 대해서 접근 권한을 허용한다.

 

 

SpringDocConfig

// 전략 //

@OpenAPIDefinition(
        info = @Info(
                // 중략 //
        ),
        tags = {
                // 중략 //
        },
        security = {
                @SecurityRequirement(name = "X-Auth-Token"),
        }
)
@SecuritySchemes({
        @SecurityScheme(name = "X-Auth-Token",
                type = SecuritySchemeType.APIKEY,
                description = "Api token",
                in = SecuritySchemeIn.HEADER,
                paramName = "X-Auth-Token"),
})
@Configuration
public class SpringDocConfig {
}

@OpenAPIDefinition

- security : Swagger-ui에서 사용할 인증 스키마를 나열한다.

 

@SecurityScheme : 인증 스키마 정보를 설정한다.

- type : 인증 타입. SecuritySchemeType(APIKEY, HTTP, OPENIDCONNECT, OAUTH2)

- in : 인증키의 입력 위치. SecuritySchemeIn(HEADER, QUERY, COOKIE)

- paramName : 인증키의 파라미터명.

 

 

위와 같이 Security 설정을 마치고 Swagger-ui에 접속하면, 인증을 위한 버튼이 추가된 것을 볼 수 있다.

 

인증 버튼이 추가되었다.

 

만약 인증 정보를 입력하지 않은 상태에서, 인증이 필요한 API를 실행하면 아래와 같이 인증 오류 응답을 받게 된다.

 

 

추가된 버튼을 클릭하면 인증키를 입력할 수 있는 팝업이 나타나며, 아래와 같이 인증키를 넣어준 후 Authorize 버튼을 눌러서 등록하고, Close 버튼을 눌러서 창을 닫는다.

 

 

이제 다시 API를 실행해 보면 정상적으로 실행되는 것을 확인할 수 있다. 이때 화면의 'Curl' 부분을 통해서 헤더에 X-Auth-Token이 첨부되는 것을 확인할 수 있다.

 

 

 

 

 

 

 

6. Login End Point 추가하기

 

Springdoc-openapi에서는 Login Endpoint를 API 목록에 포함하여 보여주기 위한 옵션과 dependency를 제공하고 있다.

 

application.yml

// 전략 //
springdoc:
  show-login-endpoint: true
// 후략 //

build.gradle

// 전략 //
implementation 'org.springdoc:springdoc-openapi-security:1.6.11'
// 후략 //

 

그러나, 결론부터 말하자면 위의 기능은 제대로 동작하지 않는다.

원인을 파악하기 위해서 springdoc-openapi-security에서 Login Endpoint를 Api 목록에 추가하는 코드를 확인해 보면 다음과 같다.

/**
 * The type Spring security login endpoint configuration.
 */
@Lazy(false)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(javax.servlet.Filter.class)
class SpringSecurityLoginEndpointConfiguration {

   /**
    * Spring security login endpoint customiser open api customiser.
    *
    * @param applicationContext the application context
    * @return the open api customiser
    */
   @Bean
   @ConditionalOnProperty(SPRINGDOC_SHOW_LOGIN_ENDPOINT)
   @Lazy(false)
   OpenApiCustomiser springSecurityLoginEndpointCustomiser(ApplicationContext applicationContext) {
      FilterChainProxy filterChainProxy = applicationContext.getBean(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, FilterChainProxy.class);
      return openAPI -> {
         for (SecurityFilterChain filterChain : filterChainProxy.getFilterChains()) {
            Optional<UsernamePasswordAuthenticationFilter> optionalFilter =
                  filterChain.getFilters().stream()
                        .filter(UsernamePasswordAuthenticationFilter.class::isInstance)
                        .map(UsernamePasswordAuthenticationFilter.class::cast)
                        .findAny();
            if (optionalFilter.isPresent()) {
               UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = optionalFilter.get();
               Operation operation = new Operation();
               Schema<?> schema = new ObjectSchema()
                     .addProperty(usernamePasswordAuthenticationFilter.getUsernameParameter(), new StringSchema())
                     .addProperty(usernamePasswordAuthenticationFilter.getPasswordParameter(), new StringSchema());
               RequestBody requestBody = new RequestBody().content(new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, new MediaType().schema(schema)));
               operation.requestBody(requestBody);
               ApiResponses apiResponses = new ApiResponses();
               apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), new ApiResponse().description(HttpStatus.OK.getReasonPhrase()));
               apiResponses.addApiResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), new ApiResponse().description(HttpStatus.FORBIDDEN.getReasonPhrase()));
               operation.responses(apiResponses);
               operation.addTagsItem("login-endpoint");
               PathItem pathItem = new PathItem().post(operation);
               try {
                  Field requestMatcherField = AbstractAuthenticationProcessingFilter.class.getDeclaredField("requiresAuthenticationRequestMatcher");
                  requestMatcherField.setAccessible(true);
                  AntPathRequestMatcher requestMatcher = (AntPathRequestMatcher) requestMatcherField.get(usernamePasswordAuthenticationFilter);
                  String loginPath = requestMatcher.getPattern();
                  requestMatcherField.setAccessible(false);
                  openAPI.getPaths().addPathItem(loginPath, pathItem);
               } catch (NoSuchFieldException | IllegalAccessException | ClassCastException ignored) {
                  // Exception escaped
                  LOGGER.trace(ignored.getMessage());
               }
            }
         }
      };
   }
}

코드를 보면 SecurityFilterChain에서 UsernamePasswordAuthenticationFilter를 찾아서 username / password 파라미터명을 알아내어 application/json 타입의 Request Body스키마를 만들고 RequestMatcher Pattern을 알아내어, API Operation을 생성하여 추가해 주는 방식임을 알 수 있다.

 

그런데, UsernamePasswordAuthenticationFilter는 formLogin을 위한 Filter이기 때문에 username과 password를 Request Parameter에서 찾아서 인증을 진행한다. 따라서 Request Body에 application/json 타입으로 전송하는 위의 스키마로는 로그인을 진행할 수 없다.(springdoc-openapi 1.6.11 기준)

 

 

그래서 여기서는 위의 코드를 변경하여 아래와 같이 별도 Customizer를 작성하고 Config에 적용하면 Login Endpoint를 API 목록에 등록할 수 있다.(springdoc-openapi-security dependency를 추가할 필요는 없다.)

 

OpenApiLoginEndpointCustomizer

// 전략 //

@Slf4j
public class OpenApiLoginEndpointCustomizer<JSON_FILTER extends UsernamePasswordAuthenticationFilter> {

    public OpenApiCustomiser loginEndpointCustomizer(JSON_FILTER filter, String tagName) {
        return openAPI -> {
            Operation operation = new Operation();

            operation.requestBody(getLoginRequestBody(filter));
            operation.responses(getLoginApiResponses());
            operation.addTagsItem(tagName);
            operation.summary("로그인");
            operation.description("사용자 계정의 인증을 처리한다.");

            PathItem pathItem = new PathItem().post(operation);
            try {
                openAPI.getPaths().addPathItem(getLoginPath(filter), pathItem);
            } catch (Exception ignored) {
                // Exception escaped
                log.trace(ignored.getMessage());
            }
        };
    }

    private String getLoginPath(JSON_FILTER filter) throws Exception {
        Field requestMatcherField = AbstractAuthenticationProcessingFilter.class.getDeclaredField("requiresAuthenticationRequestMatcher");
        requestMatcherField.setAccessible(true);
        AntPathRequestMatcher requestMatcher = (AntPathRequestMatcher) requestMatcherField.get(filter);
        String loginPath = requestMatcher.getPattern();
        requestMatcherField.setAccessible(false);
        return loginPath;
    }

    private RequestBody getLoginRequestBody(JSON_FILTER filter) {
        Schema<?> schema = new ObjectSchema();
        schema.addProperty(filter.getUsernameParameter(), new StringSchema()._default(filter.getUsernameParameter()));
        schema.addProperty(filter.getPasswordParameter(), new StringSchema()._default(filter.getPasswordParameter()));

        return new RequestBody().content(new Content().addMediaType(
                org.springframework.http.MediaType.APPLICATION_JSON_VALUE,
                new MediaType().schema(schema)
        ));
    }

    private ApiResponses getLoginApiResponses() {
        ApiResponses apiResponses = new ApiResponses();
        apiResponses.addApiResponse(
                String.valueOf(HttpStatus.OK.value()),
                new ApiResponse().description(HttpStatus.OK.getReasonPhrase())
        );
        apiResponses.addApiResponse(
                String.valueOf(HttpStatus.FORBIDDEN.value()),
                new ApiResponse().description(HttpStatus.FORBIDDEN.getReasonPhrase())
        );

        return apiResponses;
    }
}

SpringDocConfig

// 전략 //

@OpenAPIDefinition(
        info = @Info(
               // 중략 //
        ),
        tags = {
                @Tag(name = "00.SignIn", description = "로그인 기능"),
                // 중략 //
        },
        security = {
                // 중략 //
        }
)
@SecuritySchemes({
                // 중략 //
})
@Configuration
@Slf4j
@Lazy(false)
public class SpringDocConfig {
    @Bean
    @ConditionalOnProperty(Constants.SPRINGDOC_SHOW_LOGIN_ENDPOINT)
    @Lazy(false)
    OpenApiCustomiser springSecurityLoginEndpointCustomizer(JsonUsernamePasswordAuthenticationFilter authenticationFilter) {
        return new OpenApiLoginEndpointCustomizer()
                .loginEndpointCustomizer(authenticationFilter, "00.SignIn");
    }
}

 

이제 Swagger-ui에서 확인해 보면 추가된 SignIn 태그와 함께 로그인 API가 표시되는 것을 확인할 수 있으며, 로그인을 통해서 X-Auth-Token을 응답 헤더로 반환받을 수 있다.

 

 

 

 

 

 

 

7. 마치며

 

이상으로 Springdoc-openapi 및 Swagger-ui에 대한 기본적인 설정과 활용 방안을 알아보았다.

이외에도 다양한 응용 기능이나 설정 property, Annotaion 등이 제공되고 있으므로, 보다 깊게 공부하고 활용하고자 하는 경우에는 https://springdoc.org/ 를 방문해서 확인하도록 하자.