[Swagger] Swagger UI

M1naWorld·2022년 9월 21일
1
post-thumbnail

Swagger

REST API 서비스를 설계, 빌드, 문서화할 수 있도록 하는 프로젝트


Swagger Tool 종류

  • Swagger Codegen: Swagger로 정의된대로 클라이언트/서버 코드를 생성하는 CLI툴
  • Swagger UI: Swagger API 명세서를 HTML 형식으로 확인할 수 있는 툴
  • Swagger Editor: Swagger 표준에 따른 API 설계서/명세서를 작성하기 위한 에디터

📌 나는 프론트와의 협업을 위해 Swagger UI를 사용하였다.

✔️ 각 controller별로 라우터 및 주고 받는 데이터, 데이터 타입을 확인할 수 있다.


Swagger 의존성 추가

>> build.gradle

implementation (group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'){
        exclude module: 'swagger-annotations' exclude module: 'swagger-models'
    }
implementation 'io.swagger:swagger-annotations:1.6.6'
implementation 'io.swagger:swagger-models:1.6.6'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'

Swagger Config 설정

config 폴더를 만들고 SwaggerConfig 클래스를 만들어 주었다.

package com.example.tamna.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;


//http://localhost:8080/swagger-ui.html
@Configuration
@EnableSwagger2
public class SwaggerConfig {
	
    @Bean
    public Docket api()
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.tamna"))
                .paths(PathSelectors.any())
                .build();

    }

	
    // Swagger 설명 설정
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("TAMNAYEAH Open API Documentation")
                .description("더큰내일센터 회의실 예약 시스템 API")
                .version("1.0.0")
                .build();
    }


    
}

API 설명 쓰기

나는 @ApiOperation 어노테이션을 통해 간략한 설명만을 써주었다.
추가적으로 @ApiModelProperty 어노테이션을 통해 각 필드의 의미 또한 적을 수 있다.
그리고 @ApiOperation 어노테이션 또한 생략 가능하다.

@ApiOperation(value = "예약페이지 데이터", notes = "회의실 전체 데이터, 유저데이터, 현재 회의실데이터, 예약데이터, 유저들 이름 목록 데이터")
@GetMapping(value = "")
public ResponseEntity<Map<String, Object>> getRoomBookingState(@RequestParam("roomId") int roomId, HttpServletResponse response) {
	// 내용 생략
}



❗️그러나 문제가 생겼다. !!! 🥹
로그인 유지를 JWT(Json Web Token)를 통해 구현하게 되면서
원래는 프론트에서 유저데이터를 보내주면 관련 데이터를 보여주는 방식에서
백에서 JWT를 가지고 복호화를 진행하여 유저 데이터를 가져오는 방식으로 바꾸면서 전반적인 코드가 변경되었다.

결과적으로 Swagger 또한 헤더에 토큰을 실어주지 않으면 데이터를 볼 수 없는 구조가 되었다. 🥲



Swagger에 Header 입력 칸 추가 하기

우리의 로그인 유지 구조는 accessToken이 만료가되면 refreshToken을 통해 재발급 해주고 있다.
그래서 access, refresh를 둘다 header에 실어 통신하고 있어서 둘다 입력이 가능하도록 구현하였다.


package com.example.tamna.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;

//http://localhost:8080/swagger-ui.html
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${AUTHORIZATION_HEADER}")
    String AUTHORIZATION_HEADER;

    @Value("${REAUTHORIZATION_HEADER}")
    String REAUTHORIZATION_HEADER;

    @Bean
    public Docket api(){
    
    	// accessToken 헤더 값을 받기 위해 추가한 코드
        // ParameterBuilder를 통해 모든 API 전역 파라미터로 설정함
        Parameter parameterBuilder1 = new ParameterBuilder().name(AUTHORIZATION_HEADER).description("Access Token")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build();
                
		// refreshToken 헤더 값을 받기 위해 추가한 코드
        Parameter parameterBuilder2 = new ParameterBuilder().name(REAUTHORIZATION_HEADER).description("refresh Token")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build();
	
    	// 헤더 값 목록
        List<Parameter> globalParamters = new ArrayList<>();
        globalParamters.add(parameterBuilder1);
        globalParamters.add(parameterBuilder2);

        return new Docket(DocumentationType.SWAGGER_2)
                .globalOperationParameters(globalParamters) // 헤더 값 받을 수 있도록 설정
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.tamna"))
                .paths(PathSelectors.any())
                .build();

    }


    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("TAMNAYEAH Open API Documentation")
                .description("더큰내일센터 회의실 예약 시스템 API")
                .version("1.0.0")
                .build();
    }

}

아래처럼 헤더의 값을 추가할 수 있게 변하였다!


[유효한 access, refresh토큰 값이 담겼을 때]

상태코드 200과 데이터를 받아오는 걸 볼 수 있다!



[토큰에 값이 안담겼을 때]

헤더에 토큰이 안담긴경우 예외처리한 403에러가 뜨는 것을 볼 수 있다.



전역에서 헤더에 토큰 담기

위의 방식은 각 API마다 헤더에 토큰을 입력해야 하는 번거로움이 있다.
그래서 전역으로 한번에 입력하면 API마다 자동으로 헤더에 토큰을 담아주는 방식으로 코드를 변경하였다.

package com.example.tamna.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Arrays;
import java.util.List;

//http://localhost:8080/swagger-ui.html
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${AUTHORIZATION_HEADER}")
    String AUTHORIZATION_HEADER;

    @Value("${REAUTHORIZATION_HEADER}")
    String REAUTHORIZATION_HEADER;

    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.tamna"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(Arrays.asList(securityContext()))
                .securitySchemes(Arrays.asList(apiKey(), anotherApiKey()));

    }
    
	
    // accessToken 입력 칸 
    private ApiKey apiKey() {
        return new ApiKey(AUTHORIZATION_HEADER, AUTHORIZATION_HEADER, "header");
    }

	// refreshToken 입력 칸
    private ApiKey anotherApiKey(){
        return new ApiKey(REAUTHORIZATION_HEADER, REAUTHORIZATION_HEADER, "header");
    }
    

    private SecurityContext securityContext() {
        return springfox
                .documentation
                .spi.service
                .contexts
                .SecurityContext
                .builder()
                .securityReferences(defaultAuth()).forPaths(PathSelectors.any()).build();
    }


    List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope authorizationScope1 = new AuthorizationScope("global", "refreshEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        AuthorizationScope[] authorizationScopes1 = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        authorizationScopes1[0] = authorizationScope1;
        return Arrays.asList(new SecurityReference(AUTHORIZATION_HEADER, authorizationScopes), new SecurityReference(REAUTHORIZATION_HEADER, authorizationScopes1));
    }
	
   
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("TAMNAYEAH Open API Documentation")
                .description("더큰내일센터 회의실 예약 시스템 API")
                .version("1.0.0")
                .build();
    }
}

❗️주의 깊게 봐야할 것

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

첫번째 파라미터 - name 값
두번째 파라미터 - value 값에 대한 key
세번째 파리미터 - 헤더값이라는 것 표시

*ApiKey()의 두번째 파라미터에는 꼭 헤더의 토큰 키값이 들어가야 한다!!


List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope authorizationScope1 = new AuthorizationScope("global", "refreshEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        AuthorizationScope[] authorizationScopes1 = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        authorizationScopes1[0] = authorizationScope1;
        return Arrays.asList(new SecurityReference(AUTHORIZATION_HEADER, authorizationScopes), new SecurityReference(REAUTHORIZATION_HEADER, authorizationScopes1));
    }

*마지막 코드의 new SecurityReference()의 첫번째 파라미터 또한 ApiKey() 첫번째 파라미터로 설정해 준 이름이 들어가야 인식을 한다!


위와 같은 설정이 끝나면 아래 사진의 오른쪽 위에 보이는 Authorize 버튼을 누르고 각 값들을 입력해 주면 된다.🌟


Ref.
구름동산
side impact

0개의 댓글