개발 메모장

[Spring] Swagger 3.0 - API 문서화 본문

Spring

[Spring] Swagger 3.0 - API 문서화

yyyyMMdd 2024. 7. 4. 15:49
728x90
  • 데이터 수신을 위한 API 개발을 위해 이전 테스트한 Swagger보다 좀 더 나아간 Swagger 개발을 해야하는 업무가 생겼습니다.

  • 클라이언트에게 변경사항이 생길 때마다 알려주는 것보다 API문서화하여 처리하는 편이 멀리 봤을 때 간편하다고 생각하여 아래와 같이 처리하였습니다.

#. dependency 추가

 

  • Swagger 3 버전을 사용합니다.
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'

#. SwaggerConfig

  • Swagger 사용을 위한 Configuration 및 Bean 설정을 합니다.

  • Docket > securityContexts 및 securitySchemes를 제외하면 Swagger에 Authorize 버튼이 생성되지 않으니 참고바랍니다.
@Configuration
@EnableSwagger2
public class SwaggerConfig {

	@Bean
	public Docket api() {
		return new Docket(DocumentationType.OAS_30)
				// 인증 관련(추가 시 Authorize 버튼 생성)
				.securityContexts(Arrays.asList(securityContext()))
				.securitySchemes(Arrays.asList(apiKey()))
				.useDefaultResponseMessages(false)
				.select()
				// 1개의 특정 경로 지정 시
				.apis(RequestHandlerSelectors.basePackage("com.test.tt.test1.controller"))
				// 두개 이상의 경로 지정 시
				//.apis(multipleBasePackages(
                                //               "com.test.tt.test1.controller",
                                //                "com.test.tt.test2.controller"
                                //        ))
				.paths(PathSelectors.any())
				.build()
				.apiInfo(apiInfo());
	}

	// API 정보 입력
	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
				.title("Test API Swagger")
				.description("test swagger")
				.version("1.0")
				.build();
	}

	// Swagger 보안 체계 적용
	private SecurityContext securityContext() {
		return SecurityContext.builder()
				// API에 적용될 보안 권한 범위 설정
				.securityReferences(defaultAuth())	
				.build();
	}

	// 기본 인증 범위 설정
	private List<SecurityReference> defaultAuth() {
		// OAuth2 보안 범위 설정
		AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); 
		AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
		// 인증 범위를 배열에 넣기
		authorizationScopes[0] = authorizationScope;	
		// 인증 범위에 대한 객체 생성 및 리스트로 변환
		return Arrays.asList(new SecurityReference("Authorization", authorizationScopes)); 
	}

	// Swagger가 요청 시 승인하는 API키 구성
	private ApiKey apiKey() {
		return new ApiKey("Authorization", "Bearer", "header");
	}

	// Swagger에 다중 경로 지정 시 
	private Predicate<RequestHandler> multipleBasePackages(String... basePackages) {
        return input -> {
            Class<?> declaringClass = input.declaringClass();
            for (String basePackage : basePackages) {
                if (declaringClass.getPackage().getName().startsWith(basePackage)) {
                    return true;
                }
            }
            return false;
        };
    }
}

 


#.  WebMvcConfig(Swagger 경로를 읽지 못할 때)

  • 서비스 중인 기존 프로젝트 내 Swagger를 추가하다보니 기존 설정으로 인해 정상적으로 처리되지 않았습니다.

  • Unable to infer base Url ... 이런 에러가 발생하였고 URL 관련이라 생각이 들어 찾아보니 아래와 같은 방법으로 핸들러에 uri 값을 넣어줘야 했습니다.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	@Autowired
	private HttpInterceptor httpIndenter;

	@Override
	public void addResourceHandlers(final ResourceHandlerRegistry registry) {
		// 기존
		registry.addResourceHandler("/**")
			.addResourceLocations("classpath:/templates/", "classpath:/static/")
			.setCacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES));

		// swagger
		registry.addResourceHandler("swagger-ui.html")
        		.addResourceLocations("classpath:/META-INF/resources/");
		registry.addResourceHandler("/webjars/**")
        		.addResourceLocations("classpath:/META-INF/resources/webjars/");
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(httpIndenter)
				.addPathPatterns("/**")
				.excludePathPatterns(
                                    // swagger 관련 URI 추가
                                    "/swagger-ui/**",
                                    "/swagger-resources/**",
                                    "/v2/api-docs",
                                    "/webjars/**"
				);
	}
}

#. Interceptor

 

  • 기존 로직에서 특정 URI만 통과될 수 있도록 Interceptor에 설정돼 있었습니다.

  • 따라서 Interceptor에 화면에 나온 undefined uri를 예외 처리하여 접근 가능토록 하였습니다.

  • 접속 uri은 swagger-ui지만 getRequestURI()로 전달 받은 uri는 /v3/api-docs로 받으니 참고하도록 바랍니다.

  • 인증 받지 않은 이용자의 경우 API를 통신하지 못하도록 막아주는 로직도 추가합니다.
    (보통 자바의 경우 jjwt 라이브러리를 이용해 발급 및 유효여부 체크를 합니다. 현재 로직은 테스트용으로 하드코딩한 Key로 간단히 작성했습니다.)

@Component
public class HttpInterceptor implements HandlerInterceptor {

	// Authorize를 위한 key 지정
	private static final String API_KEY = "test123";

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		String requestURI = request.getRequestURI();

		// swagger
		if(requestURI.indexOf("/v3/api-docs") >= 0) {
			return true;
		}
		
		if(requestURI.startsWith("/api/v1/")) {
			String authHeader = request.getHeader("Authorization");
			if(authHeader == null || "".equals(authHeader)) {
				throw new CommonException(HttpStatus.UNAUTHORIZED, ReturnCode.INVALID_TOKEN, "인증키가 없습니다.");
			}

			if(!authHeader.equals(API_KEY)) {
				throw new CommonException(HttpStatus.UNAUTHORIZED, ReturnCode.INVALID_TOKEN, "잘못된 인증키입니다.");
			}
			return true;
		}
	}
}
728x90

#. Controller 세부 설정

  • SwaggerConfig에서 기본적인 설명을 명시하고 컨트롤러에서 어노테이션을 추가해 세부적인 설명을 명시할 수 있습니다.
@RestController
@RequestMapping("/api/v1")
@Api(tags="testApi")
@Tag(name="testApi", description="테스트 API")
public class TestController {

	@Autowired
	private TestService testService;

	@ApiOperation("테스트 데이터 송신")
	@ApiResponses(value = {
	        @ApiResponse(responseCode = "200", description = "성공했습니다!  Success", content = @Content(schema = @Schema(implementation = TestVO.class))),
	        @ApiResponse(responseCode = "404", description = "실패했습니다! Falied", content = @Content(schema = @Schema(implementation = TestVO.class))) })
	@PostMapping("/testApi")
	@Transactional
	public ResponseEntity<?> setTestData(@RequestBody TestVO param) {
		testService.setTestData(param);
		// 미리 정의한 성공 시 리턴코드 및 메시지를 리턴해줍니다.
		return ResponseEntity.ok(new ResponseData(ReturnCode.SUCCESS, Message.get(Message.REG_SUCCESS)));
	}
}

 

  • 그 외의 태그들은 아래 참조 링크를 참고 바랍니다.

#. VO  > @schema

 

  • 데이터들의 타입 및 설명을 작성하여 서로 이해할 수 있게 도와줍니다.

  • validation 라이브러리를 추가해 @NotNull 이나 @NotBlank 등으로 빈값 및 null 체크를 해주면 좋습니다.

  • 아래 내용은 위 그림파일의 Schema 내용에 적용됩니다.
@Data
public class TestVO {

	private List<test> testList;

	@Data
	public static class test {
		@Schema(description = "테스트1", required = true)
		private String test1;

		@Schema(description = "테스트2", required = true)
		private int test2;

		@Schema(description = "테스트3", required = true)
		private int test3;

		@Schema(description = "테스트4", required = true)
		private int test4;

		@Schema(description = "테스트5", required = true)
	    	private String test5;

                @Schema(description = "테스트6", required = true)
                private String test6;

                @Schema(description = "테스트7", required = true)
                private int test7;

                @Schema(description = "테스트8", required = true)
                private String test8;
	}
}

 

 

 

===========================================================
틀린 내용이 있거나 이견 있으시면 언제든 가감 없이 말씀 부탁드립니다!

참조 : https://docs.swagger.io/swagger-core/v1.5.0/apidocs/
===========================================================

 

 

 

 

 

 

 

 

 

 

728x90

'Spring' 카테고리의 다른 글

[Spring] Swagger(스웨거) 사용방법  (0) 2024.02.19
[Spring] 스프링 시큐리티(Spring Security)  (0) 2024.02.07