이번 시간에는 Swagger를 이용하여 API 문서를 만들어보도록 하겠습니다.
Swagger를 이용하면 자바 코드로 손쉽게 문서를 작성할 수 있고, 브라우저를 통해서 API를 직접 테스트해볼 수 있습니다.
먼저 dependency를 추가해주도록 하겠습니다.
// https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter
implementation 'io.springfox:springfox-boot-starter:3.0.0'
이를 이용하면 문서를 작성하기 위한 관련 의존성들을 가져오게 되고, @EnableSwagger2와 같은 어노테이션을 명시해주지 않아도 됩니다.
다음으로 스웨거 설정을 위해 config 패키지에 SwaggerConfig를 작성해줍니다.
package kukekyakya.kukemarket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.*;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.List;
@Import(BeanValidatorPluginsConfiguration.class) // 1
@Configuration
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo()) // 2
.select() // 3
.apis(RequestHandlerSelectors.basePackage("kukekyakya.kukemarket.controller"))
.paths(PathSelectors.any())
.build()
.securitySchemes(List.of(apiKey())) // 4
.securityContexts(List.of(securityContext())); // 5
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("KUKE market")
.description("KUKE market REST API Documentation")
.license("gmlwo308@gmail.com")
.licenseUrl("https://github.com/songheejae/kuke-market")
.version("1.0")
.build();
}
private static ApiKey apiKey() {
return new ApiKey("Authorization", "Bearer Token", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth())
.operationSelector(oc -> oc.requestMappingPattern().startsWith("/api/")).build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "global access");
return List.of(new SecurityReference("Authorization", new AuthorizationScope[] {authorizationScope}));
}
}
1. 스프링부트에서 작성했던 bean validation을 문서화하기 위해 BeanValidatorPluginsConfiguration @Import 해줍니다.
2. API 문서에 대한 정보를 작성해줍니다.
3. select()을 통해 반환되는 ApiSelectorBuilder를 이용하여, API 문서를 작성할 셀렉터를 지정하고 빌드할 수 있습니다. 컨트롤러 패키지를 지정해주었습니다.
4~5. 요청에 포함되어야 할 Authorization 헤더를, 전역적으로 지정하여 사용할 수 있도록 설정하였습니다.
스프링 WebMvc를 사용 중이지만, @EnableWebMvc 어노테이션을 아직 사용하고 있지 않을 경우, Swagger를 사용하기 위해서는 이를 추가해줘야 합니다.
config 패키지에 WebConfig를 작성해줍니다.
package kukekyakya.kukemarket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@EnableWebMvc
@Configuration
public class WebConfig {
}
당장 별도의 설정이 필요한 것은 아니므로, @EnableWebMvc만 등록해주겠습니다.
Swagger로 만들어진 API 문서를 사용하려면, 자동으로 문서를 생성해주는 URL에 접근할 수 있어야합니다.
config.security.SecurityConfig에 이러한 URL을 접근할 수 있도록, security를 설정해주겠습니다.
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/exception/**",
"/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**");
}
Spring Security에서 "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**"를 무시하도록 세 개의 URL 조건을 새롭게 추가해주었습니다.
이제 controller 패키지에 API 문서를 작성해보도록 하겠습니다.
SwaggerConfig에서 작성된 설정을 통해 controller 패키지는 자동으로 문서를 생성해주지만, 스웨거에서 지원해주는 어노테이션을 이용하여 더욱 세밀하게 작성해보겠습니다.
먼저 ExceptionController는 단순히 Spring Security에서 발생한 예외를, 리다이렉트를 통해 Advice에서 잡아내기 위해 작성된 것이기 때문에 문서화할 필요가 없습니다.
exception.ExceptionController는 API 문서를 만들지않도록 클래스 레벨에 @ApiIgnore를 설정해주겠습니다.
@ApiIgnore
@RestController
public class ExceptionController {
...
ExceptionController는 API 문서로 작성되지 않을 것입니다.
다음으로 SingController의 API 문서를 작성해보겠습니다.
package kukekyakya.kukemarket.controller.sign;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import kukekyakya.kukemarket.dto.response.Response;
import kukekyakya.kukemarket.dto.sign.SignInRequest;
import kukekyakya.kukemarket.dto.sign.SignUpRequest;
import kukekyakya.kukemarket.service.sign.SignService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import static kukekyakya.kukemarket.dto.response.Response.success;
@Api(value = "Sign Controller", tags = "Sign") // 1
@RestController
@RequiredArgsConstructor
public class SignController {
private final SignService signService;
@ApiOperation(value = "회원가입", notes = "회원가입을 한다.") // 2
@PostMapping("/api/sign-up")
@ResponseStatus(HttpStatus.CREATED)
public Response signUp(@Valid @RequestBody SignUpRequest req) {
signService.signUp(req);
return success();
}
@ApiOperation(value = "로그인", notes = "로그인을 한다.") // 2
@PostMapping("/api/sign-in")
@ResponseStatus(HttpStatus.OK)
public Response signIn(@Valid @RequestBody SignInRequest req) {
return success(signService.signIn(req));
}
@ApiOperation(value = "토큰 재발급", notes = "리프레시 토큰으로 새로운 액세스 토큰을 발급 받는다.") // 2
@PostMapping("/api/refresh-token")
@ResponseStatus(HttpStatus.OK)
public Response refreshToken(@ApiIgnore @RequestHeader(value = "Authorization") String refreshToken) { // 3
return success(signService.refreshToken(refreshToken));
}
}
1. 스웨거로 인해 API 문서가 작성될 것이라고 마킹해줍니다.
2. 주어진 API 오퍼레이션에 대한 설명을 입력합니다. notes에는 더욱 자세한 내용을 작성할 수 있습니다.
3. 요청에 포함되는 Authorization 헤더는 이미 전역적으로 지정되도록 설정해두었기 때문에, 해당 API에 필요한 요청 헤더는 @ApiIgnore를 선언해줍니다.
우리는 아까 SwaggerConfig에서 @Import(BeanValidatorPluginsConfiguration.class)를 해주었습니다.
이를 이용하면 @Valid가 선언된 DTO 객체들의 bean validation 조건을 문서화할 수 있습니다.
이 또한 자동으로 어느정도 작성되지만, 세밀한 제어를 위해 SignUpRequest와 SignInRequest를 수정해주겠습니다.
먼저 SignUpRequest 입니다.
@ApiModel(value = "회원가입 요청")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SignUpRequest {
@ApiModelProperty(value = "이메일", notes = "이메일을 입력해주세요", required = true, example = "member@email.com")
@Email(message = "이메일 형식을 맞춰주세요.")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;
@ApiModelProperty(value = "비밀번호", notes = "비밀번호는 최소 8자리이면서 1개 이상의 알파벳, 숫자, 특수문자를 포함해야합니다.", required = true, example = "123456a!")
@NotBlank(message = "비밀번호를 입력해주세요.")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
message = "비밀번호는 최소 8자리이면서 1개 이상의 알파벳, 숫자, 특수문자를 포함해야합니다.")
private String password;
@ApiModelProperty(value = "사용자 이름", notes = "사용자 이름은 한글 또는 알파벳으로 입력해주세요.", required = true, example = "송희재")
@NotBlank(message = "사용자 이름을 입력해주세요.")
@Size(min=2, message = "사용자 이름이 너무 짧습니다.")
@Pattern(regexp = "^[A-Za-z가-힣]+$", message = "사용자 이름은 한글 또는 알파벳만 입력해주세요.")
private String username;
@ApiModelProperty(value = "닉네임", notes = "닉네임은 한글 또는 알파벳으로 입력해주세요.", required = true, example = "쿠케캬캬")
@NotBlank(message = "닉네임을 입력해주세요.")
@Size(min=2, message = "닉네임이 너무 짧습니다.")
@Pattern(regexp = "^[A-Za-z가-힣]+$", message = "닉네임은 한글 또는 알파벳만 입력해주세요.")
private String nickname;
public static Member toEntity(SignUpRequest req, Role role, PasswordEncoder encoder) {
return new Member(req.email, encoder.encode(req.password), req.username, req.nickname, List.of(role));
}
}
요청 값의 검증을 위해 사용했던 javax.validation의 어노테이션을 바탕으로, 요청 값의 제약 조건을 문서화해주게 될 것입니다.
@ApiModel : 스웨거에서 모델로 사용하기 위한 부가적인 정보를 입력해줍니다.
@ApiModelProperty : 각각의 프로퍼티에 대해 부가적인 정보를 입력해줍니다. 값(value)과 값에 대한 설명(notes), 필수 여부(required), 예시 입력(example)을 지정해주었습니다.
다음으로 SignInRequest입니다.
@ApiModel(value = "로그인 요청")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SignInRequest {
@ApiModelProperty(value = "이메일", notes = "사용자의 이메일을 입력해주세요", required = true, example = "member@email.com")
@Email(message = "이메일 형식을 맞춰주세요.")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;
@ApiModelProperty(value = "비밀번호", notes = "사용자의 비밀번호를 입력해주세요.", required = true, example = "123456a!")
@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
}
자세한 설명은 생략하겠습니다.
다음으로 MemberController 입니다.
@Api(value = "Member Controller", tags = "Member")
@RestController
@RequiredArgsConstructor
@Slf4j
public class MemberController {
private final MemberService memberService;
@ApiOperation(value = "사용자 정보 조회", notes = "사용자 정보를 조회한다.")
@GetMapping("/api/members/{id}")
@ResponseStatus(HttpStatus.OK)
public Response read(@ApiParam(value = "사용자 id", required = true) @PathVariable Long id) { // 1
return Response.success(memberService.read(id));
}
@ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제한다.")
@DeleteMapping("/api/members/{id}")
@ResponseStatus(HttpStatus.OK)
public Response delete(@ApiParam(value = "사용자 id", required = true) @PathVariable Long id) {
memberService.delete(id);
return Response.success();
}
}
새롭게 나타난 @ApiParam(1번)만 살펴보도록 하겠습니다.
1. 요청 파라미터로 전달될 값들에 대해서 부가적인 설명을 작성해줍니다.
이제 스프링부트 애플리케이션을 실행해보겠습니다.
지금 사용하고 있는 Swagger 버전에서는, /swagger-ui/index.html 경로에 API 문서가 생성됩니다.
해당 경로로 접속해보겠습니다.
지정해뒀던 설정 값으로 API 문서가 작성되어 있습니다.
ExceptionController는 @ApiIgnore를 선언해주었기 때문에, 문서에서 제외되었습니다.
Authorize 버튼을 클릭하면 Authorization 헤더 값을 지정할 수 있습니다.
로그인 이후에 해당 버튼을 살펴보겠습니다.
먼저 Sign에서 회원가입 API를 확인해보겠습니다.
Schema를 클릭해보면, 어노테이션으로 작성된 제약 조건과 예시 데이터가 나타나고 있습니다.
로그인 API를 살펴보겠습니다.
Try it out을 눌러서 직접 서버에 요청을 보낼 수도 있습니다.
로그인 요청을 시도해보겠습니다.
Request body를 사용자 정보에 맞춰서 수정해주고, Execute를 보내면 요청이 전송됩니다.
정상적으로 로그인 요청에 대해 응답받을 수 있었습니다.
아까의 Authorize 버튼을 클릭해서 액세스 토큰을 기입해주겠습니다.
액세스 토큰을 기입해준 뒤, Authorize 버튼을 클릭해줍니다.
정상적으로 등록되었습니다.
사용자 정보 조회 API를 확인해보겠습니다.
@ApiParam으로 지정해두었던 부가 설명이 작성되어있습니다.
요청을 보내보면,
요청 메시지에 Authorization 헤더가 지정되는 것을 확인할 수 있습니다.
Schema 탭으로 이동해보겠습니다.
API 모델에 대해서 확인해볼 수 있습니다.
SignInRequest와 SignUpRequest는 별도로 작성한 부가 설명을 확인할 수 있습니다.
이 외에도 Swagger를 이용하면 다양한 내용과 방식으로 API 문서를 손쉽게 작성할 수 있습니다.
우리는 일단, Swagger는 이 정도만 작성해보고 넘어가도록 하겠습니다.
부가적인 기능은 Swagger 공식 문서를 참조 바랍니다.
앞으로 API가 추가될 때마다, Swagger를 이용하여 새로운 API에 대해 간단한 문서를 작성하며 진행하도록 하겠습니다.
다음 시간부터는 게시판 구현을 시작해보겠습니다.
* 질문 및 피드백은 환영입니다.
* 전체 소스코드에서는 여기에서 확인해볼 수 있습니다.
https://github.com/SongHeeJae/kuke-market
'Spring > 게시판 만들기' 카테고리의 다른 글
스프링부트 게시판 API 서버 만들기 (16) - 게시판 - 계층형 카테고리 - 2 (0) | 2021.12.08 |
---|---|
스프링부트 게시판 API 서버 만들기 (15) - 게시판 - 계층형 카테고리 - 1 (5) | 2021.12.08 |
스프링부트 게시판 API 서버 만들기 (13) - 코드 리팩토링 (0) | 2021.12.06 |
스프링부트 게시판 API 서버 만들기 (12) - 로그인 - 8 - 인증 및 인가 - 4(마무리) (4) | 2021.12.05 |
스프링부트 게시판 API 서버 만들기 (11) - 테스트 코드 리팩토링 (0) | 2021.12.04 |