[Toy Project] Swagger 도입 시 Spring Security 설정

최지나·2023년 11월 3일
6

제품, 카테고리, 사용자에 대한 기본적인 CRUD API만 만들었는데도, API의 수가 많아졌다 이에 이를 간단하게 문서화하여, Frontend 화면을 만드는 친구에게 남기기 위해, 그리고 나 스스로를 위해 (시간 절약,, 👻) Swagger 를 도입해보았다

그 과정에서 Swagger와 Spring Security 설정에서 막히는 부분이 있었는데 이를 해결한 과정을 기록하고자 한다!


정의

Swagger 란?

API 문서 생성 및 테스트 도구로, Spring Boot 기반의 프로젝트에서 API 문서를 자동으로 생성하고 사용자가 API를 시각적으로 탐색하고 테스트할 수 있도록 도와준다.

Spring Security란?

Spring 기반 애플리케이션에서 보안을 구현하기 위한 프레임워크로, 인증 및 권한 부여를 관리하는 데 사용된다.

사용 방법

1. 의존성 추가

  • pom.xml (maven)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

2. yml 설정

security:
  jwt:
    token:
      key: {JWT Key}
      expire-length: 3600000
  • security.jwt.token.key: JWT 토큰을 생성 및 검증할 때 사용할 비밀 키
  • security.jwt.token.expire-length: JWT 토큰의 만료 시간을 밀리초로 설정. 여기서는 3600000 밀리초(1시간)로 설정되어 있음
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    groups-order: DESC
    operationsSorter: method
    disable-swagger-default-url: true
    display-request-duration: true
    defaultModelsExpandDepth: -1
  api-docs:
    path: /api
    show-actuator: true
  default-consumes-media-type: application/json
  default-produces-media-type: application/json
  paths-to-match:
    - /**
  • springdoc.swagger-ui.path: Swagger UI에 액세스할 경로를 설정
  • springdoc.swagger-ui.groups-order: 그룹 정렬 순서를 설정
  • springdoc.swagger-ui.operationsSorter: 작업(Endpoint) 정렬 방법을 설정. method로 설정하면 HTTP 메서드 순으로 정렬됨
  • springdoc.swagger-ui.disable-swagger-default-url: 기본 Swagger URL 사용을 비활성화. 대신 /api 경로를 사용하여 Swagger에 액세스할 수 있음
  • springdoc.swagger-ui.display-request-duration: 요청 지속 시간을 표시 여부
  • springdoc.swagger-ui.defaultModelsExpandDepth: 기본 모델 확장 깊이를 설정. -1로 설정하면 모든 모델
  • springdoc.api-docs.path: Swagger 문서의 경로를 설정. /api 경로를 사용하여 API 문서에 액세스.
  • springdoc.api-docs.show-actuator: Actuator 엔드포인트를 표시할지 여부를 설정.
  • springdoc.default-consumes-media-type: 기본 요청 미디어 타입
  • springdoc.default-produces-media-type: 기본 응답 미디어 타입
  • springdoc.paths-to-match: Swagger가 문서화해야 하는 경로를 설정. /**는 모든 경로를 포함.

3. Config 추가

  • Spring Security 및 CORS 설정

    JWT (Json Web Token)에 대한 인증과 API 엔드포인트에 대한 권한 설정 및 특정 포트에서의 API 호출 허용

[Spring Security]

@Bean
public DefaultSecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .cors(cors -> corsConfigurationSource())
      .csrf(csrf -> csrf.disable())
      .exceptionHandling(req -> req.authenticationEntryPoint(jwtAuthEntryPoint))
      .authorizeHttpRequests(authorizeRequests ->
        authorizeRequests
          .requestMatchers(HttpMethod.POST, "/api/v1/user/login")
          .permitAll()
          .requestMatchers(CorsUtils::isPreFlightRequest)
          .permitAll()
          .anyRequest()
          .authenticated()
      )
      .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

[CORS 설정]

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:${port}"));
    configuration.addAllowedHeader("*");
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE"));
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
  • Swagger 설정
// API 전체 설정
@OpenAPIDefinition(
  info = @Info(
    title = "Logistics API",
    description = "제품, 상점 관리를 위한 인터페이스 규격서",
    version = "1.0"
  )
)

// API 그룹 정의. 경로가 /인 API에 대한 설정
@Bean
public GroupedOpenApi chatOpenApi() {
  String[] paths = { "/" };

  return GroupedOpenApi
    .builder()
    .group("logistics")
    .pathsToMatch(paths)
    .build();
}

// API 보안 설정
@Bean
public OpenAPI api() {
  SecurityScheme apiKey = new SecurityScheme()
    .type(SecurityScheme.Type.APIKEY)
    .in(SecurityScheme.In.HEADER)
    .name("${name}");

  SecurityRequirement securityRequirement = new SecurityRequirement()
    .addList("apiKey");

  return new OpenAPI()
    .components(new Components().addSecuritySchemes("apiKey", apiKey))
    .addSecurityItem(securityRequirement);
}

Swagger 도입 결과 및 config 수정

application.yml에 설정한 대로, http://localhost:${port}/swagger-ui/index.html 로 접속한 결과,

익숙하지만 반갑진 않은,,, 알림이 떴다

401 에러였고(the server responded with a status of 401 ()),

백앤드에서 응답은 했기에 Spring Security 설정에서 Swagger 경로도 API 요청을 허용해 주었다

 .requestMatchers(HttpMethod.GET, "/swagger-ui/*")
 .permitAll()

그랬더니 이번엔 /swagger-config API를 못 호출한다 (Failed to load remote configuration) 그래서 역시 config에 해당 경로도 요청 허용해 주었다,,,,

.requestMatchers(HttpMethod.GET, "/api/swagger-config")
.permitAll()

에러는 안좋지만,,, 달라진 에러는 또 괜찮다.. 이번엔 Failed to load API definition 에러가 떴다 그래서 Swagger config에서 설정한 group에 해당하는 (GroupedOpenApi) /api/logistics 경로까지 허용해주었다

.requestMatchers(HttpMethod.GET, "/api/logistics")
.permitAll()

결과

다음과 같은 예쁜 Swagger 페이지를 만들 수 있었다 🌺🌺 지금은 API 껍데기만 만들었으니,, 얼른 문서화해야겠다

Swagger와 Security를 나처럼 동시에 사용해서,, 비슷한 에러를 경험한 사람들에게 도움이 됐으면 좋겠다🌞

profile
의견 나누는 것을 좋아합니다 ლ(・ヮ・ლ)

1개의 댓글

comment-user-thumbnail
2023년 11월 8일

Spring Security는 몰랐을때는 당황스럽죠 ㅋㅋㅋ

답글 달기