해당 글에서는 Swagger와 Spring Security, 스프링 인터셉터를 함께 사용할 때 발생한 문제에 대해 다룹니다.
사이드 프로젝트 `모카콩`의 Wiki에 작성한 글에 해당된다.
해당 프로젝트 github: https://github.com/mocacong/Mocacong-Backend
모카콩에서는 Swagger를 이용하여 API 문서를 자동화해주고 있습니다.
인증 관련 API를 개발하던 중 `/swagger-ui/index.html` 이 아래 화면으로 뜨기 시작했습니다.
이게 어떻게 된 일일까요? 분명히 잘 보여야 할 API 명세들이 보이지 않고 Failed to load remote configuration 에러가 뜨면서 의도치 않은 화면만 보이고 있습니다.
해당 에러를 구글링하면, 아래와 같은 해결책을 제시한 결과가 주로 뜹니다.
// Make sure to add "/v3/api-docs/**" in configure method.
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/swagger-ui/**", "
/v3/api-docs/**");
}
}
출처: https://stackoverflow.com/questions/70906081/springboot-swagger3-failed-to-load-remote-configuration
하지만 모카콩은 Spring Security를 이용한 인증/인가 작업을 처리하지 않고 있을 뿐더러, Spring Security 의존성이 있다고 하더라도 WebSecurityConfigurerAdapter를 상속받아 configure 메서드를 오버라이딩하고 있지 않습니다. WebSecurityConfigurerAdapter는 2023년 현재, deprecated 되었기 때문이죠.
(만약 위처럼 configure 메서드를 오버라이딩하고 싶다면 WebSecurityCustomizer을 반환하는 메서드를 작상하고 빈으로 등록해주면 됩니다. 참고: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)
모카콩에서는 Spring Security를 이용한 BCryptPasswordEncoder를 사용하고 있습니다.
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.formLogin().disable()
.headers().frameOptions().disable();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
코드에서도 알 수 있듯이, 모카콩에서는 비밀번호 암호화를 하기 위한 인코딩 작업만 spring security 의존성을 빌리고 있습니다. 그 외에 인증 및 인가가 요구되는 작업은 spring security를 이용하고 있지 않습니다.
(단순 암호화 작업만을 하기 위해 그 무거운 Spring Security 의존성을 가지고 있는 것이냐! 에 대한 태클은 일단 여기서는 넣어두셔도 됩니다.)
따라서 스택오버플로우에서 해결방법으로 제시해준 방법은 여기서 적용되지 않았습니다.
여전히 발생하는 에러를 해결하기 위해 F12 를 켜서 개발자 도구의 Network 탭을 살펴보았습니다.
잘 보시면 swagger-config uri에서 401 Unauthorized 에러가 발생하여 Swagger api 화면이 뜨지 않은 것을 확인할 수 있었습니다. 스프링 애플리케이션 서버에서는 아예 에러가 뜨지 않은 것으로 보아, 서버 쪽에서 요청이 처리되기도 전에 막힌 것을 확인할 수 있었습니다.
모카콩에서는 인증 및 인가 작업을 interceptor를 이용하여 처리하고 있습니다. 그렇기 때문에 interceptor의 preHandle 메서드에 swagger 관련 설정들을 추가해주었습니다.
LoginInterceptor.java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (isPreflight(request) || isSwaggerRequest(request)) {
return true;
}
String token = AuthorizationExtractor.extractAccessToken(request);
jwtTokenProvider.validateToken(token);
return true;
}
private boolean isSwaggerRequest(HttpServletRequest request) {
String uri = request.getRequestURI();
return uri.contains("swagger") || uri.contains("api-docs") || uri.contains("webjars");
}
AuthConfig.java
@Configuration
public class AuthConfig implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
public AuthConfig(final LoginInterceptor loginInterceptor) {
this.loginInterceptor = loginInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs", "/error")
.excludePathPatterns("/members", "/login");
}
}
swagger 관련 요청은 LoginInterceptor의 preHandle 메서드에서 true로 early-return 시키도록 작성했습니다. 따라서 인터셉터에서의 validateToken 작업이 발생하지 않기 때문에, 사실상 AuthConfig에서 swagger 관련 설정을 excludePathPatterns 하지 않아도 작동은 문제없이 잘 됩니다. 모카콩에서는 혹시 모를 상황을 대비하여 config 설정 또한 변경해주었습니다.
만약 모카콩의 uri에 `/api/v1/` 또는 `/api` 와 같이 도입부에 해당되는 uri가 있었다면 위 문제는 발생하지 않았을 겁니다. 이러했다면 WebMvcConfigurer의 addInterceptors 메서드 오버라이딩 시에 `/**` 가 아닌 `/api/**` 와 같은 꼴의 도입부 uri의 하위 디렉토리가 추가되었을 것이기 때문입니다. /swagger-ui/index.html은 해당 패턴에 포함되지 않으므로 문제 없이 접속이 가능했겠죠. config 설정 변경을 하지 않아도 될 뿐 아니라 preHandle 메서드의 변경 역시 필요 없었을 겁니다.
하지만 아직 모카콩은 거대한 프로젝트가 아닌 소규모의 프로젝트입니다. 버전에 따른 api uri를 분리할 필요성이 없을 뿐 아니라, api server 외에 다른 infrastructure나 db, 외부 api가 아예 없는 상황입니다. 따라서 팀원들과 고민하여 최종적으로 uri 도입부에 아무것도 넣지 않는 방향을 채택한 것입니다. 이후에 모카콩의 규모가 커진다면 api 명세 및 인터셉터 역할이 달라질 수 있으리라 생각합니다.