본문 바로가기
320x100
320x100

 

https://junho85.pe.kr/1583

 

Swagger. Springfox-Swagger 그리고 Springdoc

요즘 스프링으로 프로젝트를 하면서 이런저런 지식들을 습득하고 있습니다. 예전에 스프링으로 프로젝트하시던 분들이 swagger를 이용해서 API문서를 만들던 것을 보고 swagger라는 것에 대해 듣게

junho85.pe.kr

일단 Swagger는 Springfox-swagger 2.x 3.x버전과 Springdocs-OpenAPI 두개의 버전이 있습니다!

 

둘다 사용하는 Bean이 다른데요.

위처럼 TEST-API / 인증이 필요한 API / 인증이 불필요한 API 이렇게 그룹화를 할 수 있습니다.

TEST-API에는 공통적으로 쓰이는 API를 두면 좋겠죠?(ComponentScan 등으로 부모 프로젝트의 특정 패키지를 가리켜주면 됩니다

 

1. Springfox Swagger 3.0

implementation 'io.springfox:springfox-boot-starter:3.0.0'

# SwaggerConfig.java

package com.boki.realworld.config;

import com.boki.realworld.resolver.LoginUser;
import com.boki.realworld.resolver.OptionalUser;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestAttribute;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

@RequiredArgsConstructor
@Configuration
public class SwaggerConfig {

    private final ServerProperties serverProperties;

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
            .groupName("TEST-API")
            .apiInfo(apiInfo())
            .securitySchemes(List.of(apiKey()))
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.boki.realworld.api.test"))
            .paths(PathSelectors.any())
            .build()
            .ignoredParameterTypes(LoginUser.class, OptionalUser.class, RequestAttribute.class)
            .useDefaultResponseMessages(false);
    }

    private ApiInfo apiInfo() {
        Integer port = serverProperties.getPort();
        return new ApiInfoBuilder()
            .title("RealWorld Swagger Open API Docs")
            .description(
                "<a href=\"http://localhost:" + port
                    + "/swagger-ui/index.html#/\" target=\"_self\">Boki's RealWorld API Specification</a>")
            .version("1.0")
            .termsOfServiceUrl("http://swagger.io/terms/")
            .contact(new Contact("boki", "https://code-boki.tistory.com/", "lsb530@naver.com"))
            .build();
    }

    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));
    }

    private ApiKey apiKey() {
        return new ApiKey("Authorization", "Authorization", "header");
    }

    public static Predicate<RequestHandler> withoutMethodAnnotation(
        final Class<? extends Annotation> annotation) {
        return input -> !input.isAnnotatedWith(annotation);
    }

    @Bean
    public Docket NonRequireSecurityApi() {
        return new Docket(DocumentationType.OAS_30)
            .groupName("NonRequireSecurityApi")
            .apiInfo(noAuthApiInfo())
            .select()
            .apis(withoutMethodAnnotation(PreAuthorize.class))
            .paths(PathSelectors.any())
            .build()
            .ignoredParameterTypes(LoginUser.class, OptionalUser.class, RequestAttribute.class)
            .useDefaultResponseMessages(false);
    }

    @Bean
    public Docket RequireSecurityApi() {
        return new Docket(DocumentationType.OAS_30)
            .groupName("RequireSecurityApi")
            .apiInfo(requireAuthApiInfo())
            .securityContexts(List.of(securityContext()))
            .securitySchemes(List.of(apiKey()))
            .select()
            .apis(RequestHandlerSelectors.withMethodAnnotation(PreAuthorize.class))
            .paths(PathSelectors.any())
            .build()
            .ignoredParameterTypes(LoginUser.class, OptionalUser.class, RequestAttribute.class)
            .useDefaultResponseMessages(false);
    }

    private ApiInfo noAuthApiInfo() {
        return new ApiInfoBuilder()
            .title("API Specs Without Auth")
            .description("<h2>인증이 필요하지 않은 Api 목록들</h2>")
            .version("1.0")
            .termsOfServiceUrl("http://swagger.io/terms/")
            .contact(new Contact("boki", "https://code-boki.tistory.com/", "lsb530@naver.com"))
            .build();
    }

    private ApiInfo requireAuthApiInfo() {
        return new ApiInfoBuilder()
            .title("API Specs With Auth")
            .description("<h2>인증이 필요한 Api 목록들<h2>")
            .termsOfServiceUrl("http://swagger.io/terms/")
            .contact(new Contact("boki", "https://code-boki.tistory.com/", "lsb530@naver.com"))
            .build();
    }

}

# Controller

@SecurityRequirement(name = "Authorization")
    @GetMapping("/test")
    public ResponseEntity<Object> test() {
        return ResponseEntity.ok("test!!");
    }

    @PreAuthorize("isAnonymous()")
    @GetMapping("/test0")
    public String test0() {
        return "test0";
    }

    @PreAuthorize("isAuthenticated()")
    @GetMapping("/test1")
    public String test1() {
        return "test1";
    }

    @PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')")
    @GetMapping("/test2")
    public String test2() {
        return "test2";
    }

SecurityReference 이름을 Authorization으로 만들었기 때문에 인증(자물쇠)이 필요한 요청에 한해서는

@SecurityRequirement(name = "설정")을 붙여줘야된다

 

2. Springdoc-openapi

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

# SwaggerConfig.java

    @Bean
    public GroupedOpenApi testApi() {
        return GroupedOpenApi.builder()
            .group("TEST-API")
            .packagesToScan("com.boki.realworld.api.test")
            .build();
    }

    @Bean
    public GroupedOpenApi nonTestApi() {
        return GroupedOpenApi.builder()
            .group("API")
            .packagesToExclude("com.boki.realworld.api.test")
            .build();
    }

    @Bean
    public OpenAPI openAPI() {
        Info info = new Info().title("").version("1")
            .description("")
            .termsOfService("http://swagger.io/terms/")
            .contact(new Contact().name("boki").email("lsb530@naver.com"))
            .license(new License().name("").url(""));

        return new OpenAPI()
            .info(info)
            .components(new Components()
                .addSecuritySchemes("Authorization", new SecurityScheme()
                    .type(SecurityScheme.Type.APIKEY)
                    .in(SecurityScheme.In.HEADER)
                    .name("Authorization")))
            .addSecurityItem(new SecurityRequirement().addList("Authorization"));
    }

 

다음번에는 RestDocs로 돌아오겠다!! 안녕

320x100

댓글