Spring Boot 3.2.5에서 (Spring Security 6.2.4) random port로 Mock mvc test 진행 시 IllegalArgumentException 발생
java.lang.IllegalArgumentException : Failed to find servlet [] in the servlet context
main 쪽에 구성한 Web Security Configuration의 (@EnableWebSecurity) SecurityFilterChain을 제대로 못 불러오는(?) 어떤 버그가 있는 것 같고, 마침 며칠 전에 해결이 된 것 같아 간략히 기록해 둔다 (영어를 읽기 힘든 개발자라.. 슬픔...ㅠㅠ)
cf. Spring Security GitHub Issue (접은 글은 AI 도움으로 번역한 내용)
DispatcherServletDelegatingRequestMatcher causes errors when there is more than one ServletContext · Issue #14418 · spring-pro
Describe the bug In a Spring Boot application with multiple servlets registered to the context (DispatcherServlet and at least one other), an IllegalArgumentException with message Failed to find se...
github.com
2024/01/09 LewisMcReu: DispatcherServlet과 최소 하나 이상의 다른 서블릿이 등록된 Spring Boot 애플리케이션에서, @SpringBootTest (webEnvironment = RANDOM_PORT)로 테스트를 실행할 때 "IllegalArgumentException" 예외가 발생합니다. 예외 메시지는 ```Failed to find servlet [] in the servlet context```입니다.
Spring Boot 3.0.2에서 Spring Boot 3.2.1 (Spring Security 6.2.1)로 업그레이드한 후 발생하기 시작했습니다.
이 예외는 DispatcherServletDelegatingRequestMatcher에서 발생하며, 이는 MockMvcRequestBuilders 클래스로 생성된 표준 MockHttpServletRequest 인스턴스와의 호환성 문제 때문입니다. 이러한 인스턴스는 servletName이 빈 문자열인 일반적인 HttpRequestMapping을 가지고 있어, Matcher가 ServletRegistration을 찾지 못하고 예외를 발생시킵니다.
2024/02/16 seschi98: 문제가 처음 발생한 정확한 spring-boot / spring-security 버전을 찾는데 노력했습니다.
시나리오:
* Spring Boot 3.1.x (테스트용, 3.2.x에서도 마찬가지로 문제 발생)
* 메인 서버와 다른 포트에서 관리 서버 실행 (제 경우 :8081)
* SecurityFilterChain 구성
* 추가 서블릿 구성
Spring Boot 3.1.1 (Security 6.1.1) 모든 게 예상대로 작동 (시작 시 오류 없고, 정상 응답 수신)
Spring Boot 3.1.2 (Security 6.1.2) 서버가 시작되지 않음
Spring Boot 3.1.3 (Security 6.1.3) 같은 문제, 오류 메시지만 개선됨
( Boot 3.1.4 (Security 6.1.4), Boot 3.1.5 (Security 6.1.5), Boot 3.1.6 (Security 6.1.5) 동일 )
Spring Boot 3.1.7 (Security 6.1.6) 서버 시작되지만 HTTP 500 오류 수신
application.yaml에서 ```management.server.port: 8081``` 라인을 제거하면, 정상적으로 작동 (물론 이 경우 포트는 8080)
2024/03/27 amb-sebastian-podgorski-pt: ```config.requestMatchers( "/hello" ).permitAll();``` 사용하면 AntPathRequestMatcher 생성이 AbstractRequestMatcherRegistry에 위임됩니다. 그다음에는 resolve 메서드가 호출되는데, 이 메서드는 컨텍스트에 구성된 DispatcherServlet이 하나만 있는지 확인합니다. 그리고 추가적인 DispatcherServlet을 선언하면 ant와 mvc 매퍼가 DispatcherServletDelegatingRequestMatcher 내에 래핑됩니다. 이 Matcher는 dispatcherServletRegistration이라는 이름의 Bean을 찾는데, 이 Bean은 지연 초기화(LAZY MODE)로 설정되어 있습니다.
다시 말해, 이 Bean은 첫 번째 다른 포트에(예: localhost:8081/actuator/health) 대한 요청 후에 초기화되므로, 애플리케이션 초기화 중에 구성된 컨텍스트에서 찾을 수 없습니다. 그 결과 이 문제가 발생하게 됩니다. 가장 간단한 해결책은 직접 AntPathRequestMatcher를 선언하는 것입니다.
예: config.requestMatchers( AntPathRequestMatcher.antMatcher( "/hello" ) ).permitAll();
2024/06/04 jzheaux: 약간의 배경 설명을 드리면, Spring Security에서 requestMatchers(String)을 사용할 때는 해당 엔드포인트가 MVC인지 아닌지를 알 수 없습니다. 따라서 MVC와 non-MVC 엔드포인트가 모두 있는 애플리케이션(이 경우 사용자 정의 서블릿)에서는 Spring Security에 더 많은 정보가 필요합니다.
Spring Security에서 MockMvc 테스트 내에서 실행 중인 것을 활용할 수 있을 것 같지만, 이에 대한 일반적인 방법은 명확하지 않습니다. Spring Web 팀에 문의하여 의견을 들어보겠습니다.
... [ 아래 해결 방법으로 이어짐 ]
문제 제기와 원인 분석 과정은 접은 글을 참조하고, 전체 내용을 종합해서 해결 방법을 간단하게 적으면 다음과 같다
- 랜덤 포트를 쓰지 말고 8080으로 고정하거나
- MockHttpServletRequest를 직접 정의하고 송신할 때마다 지정한다
Servlet을 직접 정의하는 것과 관련해서는 다음 두 가지가 제시되었다
(1) Spring Security 버전을 최신 SNAPSHOT으로 업데이트한 경우 다음과 같이 수정할 수 있다
(Maven 저장소에 올라온 것 같진 않던데 GitHub 소스를 직접 받아서 사용할 경우를 말하는 건지.. 확실히는 잘 모르겠다)
this.mvc.perform(get("/").with((request) -> {
request.setHttpServletMapping(new MockHttpServletMapping("/", "/", "dispatcherServlet", MappingMatch.PATH));
return request;
})).andExpect(status().isOk());
(2) 또는 다음과 같이 좀 더 일반화된 방법으로 수정할 수도 있다
private static RequestPostProcessor mvcMapping() {
return (request) -> {
String matchValue = request.getRequestURI();
String servlet = "dispatcherServlet";
String pattern = request.getServletContext().getServletRegistration(servlet).getMappings().iterator().next();
HttpServletMapping mapping = new MockHttpServletMapping(matchValue, pattern, servlet, MappingMatch.PATH);
request.setHttpServletMapping(mapping);
return request;
};
}
// ...
this.mvc.perform( get("/")
.with( mvcMapping() )
.andExpect( status().isOk() )
);
// ...
'개발 > 자바 Java' 카테고리의 다른 글
[SQL] 차례차례 최종 값을 확인해야 할 때 (0) | 2024.11.17 |
---|---|
따라가며 만들기 + 마이그레이션 연습 (Spring Boot, AWS) (2) | 2024.06.16 |
[Ubuntu] 따라하기 + 배포 연습 (Spring Boot) (0) | 2024.06.16 |
[Java 중급] deprecated Spring Security Configuration (0) | 2024.06.06 |
[IntelliJ] 인텔리제이 초기 설정 / 옵션 (개인 기록용) (1) | 2024.05.27 |
[jQuery] 코드 블럭 복사하기 - (문제 해결) 플러그인과 함께 사용하기 (0) | 2024.05.18 |