[버티] Spring Security와 Swagger 통합 시 발생한 이슈 해결기

Spring Security와 Swagger 통합 이슈


Spring Boot와 Spring Security, 그리고 Swagger(OpenAPI)를 함께 사용하기 위해 공부하고 구현하며 여러 이슈가 발생했습니다. 따라서 제가 프로젝트를 수행하며 “소셜 로그인 기능을 구현하면서 Swagger와 관련된 문제들을 어떻게 인지하고 해결했는지”를 기록해 보았습니다.


1. Swagger 문서 접근 제한 및 인증 설정 이슈

1.1. 문제 상황

Spring Security 환경에서 Swagger 문서(/swagger-ui/index.html, /v3/api-docs)에 접근하려면 인증이 필요한 상황이 발생하였습니다. 이는 프론트엔드 개발자와 API 명세를 공유하고 협업하는 데 불편을 초래할 것이라 판단했습니다. 로그인 과정을 거치지 않으면 Swagger UI 자체가 열리지 않았기 때문에, 프론트엔드가 API 테스트를 원활히 진행할 수 없었습니다.

그래서 개발 환경(dev profile) 에 한해 Swagger 관련 인증을 해제했습니다. 이렇게 하니 Swagger 문서를 보다 자유롭게 접근할 수 있었고, API 확인과 테스트 속도도 한결 빨라졌습니다.

다만 이러한 설정을 그대로 운영 환경에 적용할 경우, 민감한 정보 노출의 위험이 있기 때문에, 배포 시에는 Swagger 접근을 차단하거나 관리자만 접근 가능하도록 제한할 계획입니다. 이처럼 환경에 따라 적절하게 보안 수준을 조절하는 방식은 실제 현업에서도 자주 사용하는 전략이기도 합니다.

추가적으로, 설정 과정 중 다음과 같은 오류가 발생했습니다:

IllegalStateException: Can't configure mvcMatchers after anyRequest.

1.2. 👀 원인 분석

  1. Spring Security는 기본적으로 모든 요청에 대해 보안 필터를 적용하여, 인증되지 않은 사용자의 접근을 차단합니다. Swagger 문서 관련 URL 또한 별도의 예외 처리를 하지 않으면 일반적인 보호 대상 경로로 인식되어 접근이 제한됩니다.
  2. Spring Security에서 .anyRequest() 호출 이후 .requestMatchers()를 선언한 것이 문제
    • URL 매칭 규칙상 구체적인 경로를 먼저 선언하고, 마지막에 anyRequest()를 사용해야 합니다

1.3. 해결 방법

SecurityConfig 클래스에서 Swagger 관련 경로에 대한 접근을 허용하도록 다음과 같이 설정했습니다:

  • Swagger 관련 경로를 먼저 선언한 후, 마지막에 anyRequest()를 설정하는 방식으로 해결했습니다:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers(
                "/swagger-ui/**",
                "/swagger-resources/**",
                "/v2/api-docs",
                "/v3/api-docs",
                "/webjars/**"
            ).permitAll()
            .anyRequest().authenticated()
        );

    return http.build();
}

이 설정으로 인해 Swagger 문서에 로그인 없이 접근 가능하게 되었으며, 프론트엔드와의 협업도 원활해졌습니다.

“개발 생산성과 보안의 균형”이라는 관점에 대해 생각하게 되었습니다.


2. OAuth2 엔드포인트가 Swagger에 표시되지 않는 문제

2.1. 문제 상황

Spring Security가 자동으로 생성해주는 OAuth2 엔드포인트(/oauth2/authorize/{provider}, /login/oauth2/code/{provider})는 컨트롤러 기반이 아니기 때문에 Swagger 문서에 자동으로 포함되지 않았습니다.

2.2. 👀 원인

Swagger는 기본적으로 @RestController@RequestMapping 애노테이션이 붙은 메서드를 기준으로 API 문서를 생성합니다. 하지만 Spring Security의 OAuth2 로그인 경로는 Security 설정 내부에서 동적으로 구성되기 때문에 Swagger가 자동으로 인식할 수 없습니다.

2.3. 해결 방법

SwaggerConfig에서 수동으로 OpenAPI 객체에 경로를 등록하여 문서화했습니다:

@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI customOpenAPI() {
        OpenAPI openAPI = new OpenAPI()
                .info(new Info()
                        .title("Burty Server API")
                        .version("1.0.0")
                        .description("버티 서버 API 문서"))
                .paths(new Paths());

        openAPI.getPaths().addPathItem("/oauth2/authorize/{provider}", new PathItem().get(new Operation().summary("OAuth2 인증 요청")));
        openAPI.getPaths().addPathItem("/login/oauth2/code/{provider}", new PathItem().get(new Operation().summary("OAuth2 로그인 콜백")));

        return openAPI;
    }
}

3. Swagger UI 그룹명 ‘default’ 표시 이슈

3.1. 문제 상황

Swagger UI에서 각 API가 default라는 그룹으로 묶여 가독성이 떨어졌습니다.

3.2. 👀 원인

Swagger는 명시적으로 @Tag 애노테이션이 부여되지 않은 컨트롤러의 경우, 자동으로 default라는 태그로 묶어 표시합니다. 이는 다양한 기능의 API가 하나의 그룹으로 합쳐져 구분이 어렵게 됩니다.

3.3. 해결 방법

컨트롤러에 @Tag 애노테이션을 추가하고, 각 API에 tags를 명시함으로써 그룹을 명확하게 분리했습니다:

@Tag(name = "인증", description = "사용자 인증 관련 API")
@RestController
@RequestMapping("/auth")
public class AuthController {

    @Operation(summary = "카카오 로그인 URL 반환", tags = {"인증"})
    @GetMapping("/kakao")
    public ResponseEntity<String> kakaoLogin() {
        ...
    }
}

4. Swagger UI에 Authorization 설정 추가

4.1. 문제 상황

프론트엔드에서 JWT 인증이 필요한 API를 Swagger UI로 테스트하고자 했으나, Authorization 정보를 입력할 수 있는 UI가 없었습니다.

4.2. 👀 원인 분석

  • Swagger 기본 설정에는 Authorization 헤더 입력 기능이 포함되어 있지 않습니다.
    • JWT 기반 인증이 필요한 API에도 별도의 인증 설정을 명시하지 않으면 Swagger UI에서 테스트가 불가능합니다.
  • 따라서 개발 및 테스트 과정에서 프론트엔드가 토큰을 포함한 요청을 손쉽게 재현할 수 있도록 하기 위해 보안 스키마 설정이 필요했습니다.

  • 프론트엔드에서 JWT 인증이 필요한 API를 Swagger UI로 테스트하고자 했으나, Authorization 정보를 입력할 수 있는 UI가 없었습니다.

4.3. 해결 방법

1) 전역 보안 스키마 정의

@Configuration
@SecurityScheme(
        name = "bearerAuth",
        type = SecuritySchemeType.HTTP,
        scheme = "bearer",
        bearerFormat = "JWT"
)
public class OpenApiConfig {
    // 어노테이션으로 설정
}

2) 개별 API에 적용

@Operation(security = { @SecurityRequirement(name = "bearerAuth") })
@PutMapping("/profile")
public ResponseEntity<?> updateUserProfile(...) {
    // 메서드 구현
}

이 설정을 통해 Swagger UI에서도 인증이 필요한 API를 명확히 구분하고, 테스트 시 토큰을 직접 입력할 수 있도록 구성할 수 있었습니다.


5. 실무 관점에서의 Swagger 접근 관리 전략

  • 개발 환경에서는 Swagger 접근을 자유롭게 열어두고 프론트엔드 테스트를 용이하게 함
  • 운영 환경에서는 보안을 위해 관리자 인증 또는 방화벽 제한을 적용하는 것이 일반적
  • JWT 기반 인증 API는 Swagger UI에서도 Authorization 헤더를 통해 테스트할 수 있도록 설정

6. 정리하며

Swagger와 Spring Security를 함께 사용할 때 마주친 실제적인 문제들을 해결하면서, 보안과 개발 편의성 사이에서 균형을 맞추는 경험을 할 수 있었습니다. 특히 프론트엔드 협업 관점에서 Swagger 인증 해제와 JWT 연동 설정은 협업 효율을 높이는 핵심 포인트였습니다.

이러한 설정 경험은 포트폴리오에 실질적인 문제 해결 능력으로 정리할 수 있었고, 실무에서도 유용하게 활용될 수 있는 기반이 되었습니다.