Spring Boot 2.7.3
Gradle
Open Api Specification(OAS), 이는 RESTful API spec을 정의된 규칙에 맞게 json이나 yaml로 표현하는 방식을 의미한다. Swagger는 OAS를 위한 프레임워크이며, API들이 갖고 있는 specification을 정의할 수 있는 툴들 중 하나이다. API의 문서를 자동화뿐만 아니라, 파라미터를 넣어보고 테스트를 진행할 수 있다. API 문서를 작성하는 시간을 절약할 수 있고, API 정보를 실시간으로 유지할 수 있다는 장점이 있다.
Spring에서 Swagger를 쉽게 사용할 수 있도록 도와주는 라이브러리로 Springdoc과 Springfox가 존재한다. 현재 2022년을 기준으로 사람들은 Springfox를 더 많이 사용하고 있기 때문에 Springfox를 적용해보겠다.
implementation 'io.springfox:springfox-boot-starter:3.0.0'
@Configuration
public class SwaggerConfig {
private Docket testDocket(String groupName, Predicate<String> selector) {
return new Docket(DocumentationType.OAS_30)
.apiInfo(this.apiInfo(groupName)) // ApiInfo 설정
.useDefaultResponseMessages(false)
.groupName("testApi")
.select()
.apis(RequestHandlerSelectors.
basePackage("패키지명"))
.paths(PathSelectors.ant("/api/**")).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("제목")
.description("설명")
.version(version)
.contact(new Contact("이름", "홈페이지 URL", "e-mail"))
.build();
}
}
@EnableSwagger2
는 사용하지 않아도 된다.com.example.demo.XXXApiController
) 만약, RequestHandlerSelectors.any()
로 설정한다면 전체 API에 대한 문서를 Swagger를 통해 나타낼 수 있다.PathSelectors.any()
로 설정한다면 패키지 안의 모든 API에 대한 문서를 나타낼 수 있다.이제 Swagger에 나타낼 Controller를 작성해보겠다.
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.Data;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
//Controller
@Api(tags = "Controller 이름")
@RestController
public class TestApiController {
@Operation(summary = "요약", description = "설명")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "500", description = "Server Error")
})
@PostMapping("/api/test")
public ResponseDto exampleMethod(
@Parameter(description = "파라미터 설명", example = "1") @Valid RequestDto requestDto
) {
return new ResponseDto();
}
}
// RequestDto
@Data
class RequestDto {
@ApiModelProperty(value="값1 설명", example="0")
@NotNull
private Long reqVar1;
@ApiModelProperty(value="값2 설명", example="example string")
private String reqVar2;
}
// ResponseDto
@Data
class ResponseDto {
@ApiModelProperty(value="값1 설명", example="0")
private Long resVar1;
@ApiModelProperty(value="값2 설명", example="example string", hidden = true)
private String resVar2;
}
각각의 어노테이션에 다양한 값이 있어 자세히 나타낼 수 있다. 일부만 간단하게 작성해보았다.
tags
값으로 Swagger에 나타낼 Controller의 이름을 지정했다.summary
)와 설명(decription
)을 작성했다.decription
)와 예시(example
)을 작성했다.decription
)와 예시(example
)을 작성했다.애플리케이션을 실행시키고 http://localhost:8080/swagger-ui/index.html로 접속해 Swagger를 실행시키면 작성한 API에 대한 문서가 생성된 것을 볼 수 있다.
Controller 부분과 Schemas 부분을 간단히 들여다보겠다. 직관적이라 쉽게 이해할 수 있다.
@Valid
어노테이션에 의해 RequestDto의 @NotNull
어노테이션이 적용되어 resVar1 값 옆에 *required라고 명시된 것을 확인할 수 있다.
@ApiResponse로 작성한 반환 값들에 대한 설명을 확인할 수 있다. Docket 객체의 useDefaultResponseMessages() 값을 true로 설정해두었다면 200, 401, 403, 404에 대한 기본 응답 메세지 또한 확인할 수 있다.
응답 데이터에 대한 정보를 확인할 수 있다. reqVar2 값을 hidden=true
로 설정해두었기 때문에 화면에 보이지 않는다. 요청DTO에도 설정해둘 수 있다.
왼쪽 상단의 "Try it out"을 눌러 API를 실행해보겠다.
ApiInfo 객체를 이용해 Swagger 위에 나타나는 부분을 커스터마이징 할 수 있다.
@Configuration
public class SwaggerConfig {
private Docket testDocket(String groupName, Predicate<String> selector) {
return new Docket(DocumentationType.OAS_30)
.apiInfo(this.apiInfo(groupName)) // ApiInfo 설정
.useDefaultResponseMessages(false)
.groupName("testApi")
.select()
.apis(RequestHandlerSelectors.
basePackage("패키지명"))
.paths(PathSelectors.ant("/api/**")).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("제목")
.description("설명")
.version(version)
.contact(new Contact("이름", "홈페이지 URL", "e-mail"))
.build();
}
}
생성자를 이용해 모든 정보를 넣거나, 혹은 build() 메소드를 통해 원하는 정보를 넣어 객체를 생성할 수 있다.
@Configuration
public class SwaggerConfig {
private Docket testDocket(String groupName, Predicate<String> selector) {
return new Docket(DocumentationType.OAS_30)
.useDefaultResponseMessages(false)
.securityContexts(List.of(this.securityContext())) // SecurityContext 설정
.securitySchemes(List.of(this.apiKey())) // ApiKey 설정
.groupName("testApi")
.select()
.apis(RequestHandlerSelectors.
basePackage("패키지명"))
.paths(PathSelectors.ant("/api/**")).build();
}
// JWT SecurityContext 구성
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return List.of(new SecurityReference("Authorization", authorizationScopes));
}
// ApiKey 정의
private ApiKey apiKey() {
return new ApiKey("Authorization", "Authorization", "header");
}
}
저는 JWT 토큰 방식을 이용해 Authorization Header를 "Bearer {Access Token}" 형식으로 받아와 인증을 처리했기 때문에, 이 글을 보시는 분들과 ApiKey를 정의하는 방식이 다를 수 있습니다.
Swagger를 실행해보면 오른쪽에 자물쇠 모양의 버튼이 생성된 것을 확인할 수 있다.
토큰을 입력하고 Authorize 버튼을 누르면 API에 대한 모든 요청이 HTTP 헤더에 토큰이 자동으로 포함된다.
인증 후, 자물쇠가 잠겨있는 것을 확인할 수 있다.
운영 환경에서는 Swagger가 작동하지 않도록 처리하는 것이 필요하다. 이 문제 때문에 며칠동안 고민이 많았는데, SwaggerConfig 클래스에 @Profile
어노테이션을 달았는데도 우선 "작동"이 된다는 점이었다.
SwaggerConfig
@Profile({"!prod"})
@Configuration
public class SwaggerConfig {
// ...
}
예상대로라면 prod
값일 때는 Swagger가 작동되지 않을 줄 알았다.
운영 환경
spring.profiles.active: prod
개발 환경
spring.profiles.active: dev
??????
.. 그리고 모든 Controller가 똑같이 보이고 똑같이 실행됐다. SecurityConfig에서 IP 등을 사용해 접근을 직접 제어하는 방법도 사용해보았지만, IP가 바뀔 때도 있을 것이고, 손이 많이 가는 방법이라 사용하고 싶지 않아서 더 고민을 하게 됐다.(API 문서 개발이 급해 그냥 커밋해버리고 싶다는 생각을 12129038109번정도 했다🤤...)
사실 이거 기록해두려고 글을 작성한 것 같다.. 구글링을 열심히 하다가!! 이 글을 보고 방법을 참고해 해결했다!! Docket 객체의 enabled()
메소드를 활용하는 방법이었다.
@Configuration
public class SwaggerConfig {
@Profile({"test || dev"})
@Bean
public Docket indexApi() {
return docket("default", PathSelectors.ant("/api/**"));
}
@Bean
@Profile({"!test && !dev"})
public Docket disable() {
return new Docket(DocumentationType.OAS_30).enable(false);
}
운영 환경에서 아예 Swagger 페이지에 접근이 불가능해진다!!!
이상 Swagger 적용기였다.. 끝까지 글을 봐주신 분들 감사합니다!