부끄러운 이야기이지만..
스프링 버전 마이그레이션 업무에 실패했습니다.
부족함을 인정하고,
왜 실패하였는지를 공유해보려고 합니다.
회사에서 기존 사용하던 기술스택은 대략 아래와 같습니다.
JDK 1.8
Spring Legacy 4.x
Tomcat 9
JSP
아직도 이런기술을 쓰는 회사가 있어?!
라고 생각하실 수도 있지만....
네.. 저희가 그랬습니다.
레거시 환경에 레거시 코드가 덕지덕지 붙어있긴 하지만,
그래도 나름 20년 가까운 시간동안
대기업과 금융권 보안검사를 통과해온 구성은 탄탄한 프로젝트 였습니다.

비록 사용하는 기술은 최신의 프레임워크가 아닐지라도
안성재쉐프님의 말을 빌려서
도메인이 탄탄하고, 깊은 역사가 담겨있었던 프로젝트였다는 점에서
자부심있게 운영하고 유지보수하고 있었던 프로젝트였습니다.
기존까지는 왠만하면 내부망에 배포를 진행하였기 때문에,
또한 패키지 형태로 납품해 왔기 때문에
웹취약점 점검은 수행하되 오픈소스 취약점 점검은 진행하지 않았습니다.
그러나, 최근 외부망에 프로젝트를 배포하야 하는 사업을 시작하면서
기존에 사용하던 Spring-core, Spring-security 등 스프링 관련 의존성이 문제점으로 잡히기 시작했습니다.
아무래도 옛 기술을 사용하는만큼
Spring 6버전은 너무 과격하다. Spring5로 올려보자. 라는 의견이 나와서
Spring-core 버전을 5으로 올리는 작업을 우선적으로 진행하였습니다.
Spring-core의 메이저버전을 변경하는 것에대한 막연한 두려움이 있었지만,
예상보다 쉽게 작업할 수 있었습니다.
(이 과정에서 jdk1.8 -> 17로의 전환이 이루어 졌으며, 이 부분도 IDE 설정 문제 외엔 큰 어려움은 없었습니다.)
스프링시큐리티 버전을 올린 후
애플리케이션 구동시
The request was rejected because the URL contained a potentially malicious String “//”
와같은 에러메세지가 발생
SpringBoot 2.6버전부터 url에 더블슬래시("//")를 허용하지 않기때문에 발생한 에러로
다행히도 저말고도 다른분들이 겪었던 이슈였기 때문에
손쉽게 해결할 수 있을 줄 알았습니다....
https://godd.tistory.com/40
https://gwanhyeon.github.io/Spring-20210528-spring-double-slash/
대부분의 솔루션으로 아래와 같은 자바코드를 통해
url에 더블슬래시를 허용해주는 옵션을 주는 방식을 택하고 있었습니다.
@Override
public void configure(WebSecurity web) throws Exception {
web.httpFirewall(defaultHttpFirewall());
}
@Bean
public HttpFirewall defaultHttpFirewall() {
return new DefaultHttpFirewall();
}
과거의 프로젝트였던 만큼
security의 구성코드가 java Config대신 xml코드로 관리되고 있었습니다.
xml로 해당 옵션을 주려고 노력해보았으나, 해결이 쉽지 않았기에
결국 xml로 구성된 코드를 config방식으로 변경하는 작업을 진행했습니다.
다행히 최신의 스프링시큐리티의 구성은 사이드프로젝트로 자주 해 보았기에
chatgpt를 통해서 마이그레이션에 필요한 큰 뼈대를 작성하고,
세세한 내용은 한개씩 비교해보면서 변경할 수 있었습니다.
시큐리티 설정을
xml->@Configuration으로 변경하면서 jsp페이지에 csrf토큰이 내려오지 않는 현상
한참 해멘 결과 찾은 원인은
Config클래스를 Bean으로 등록하지 않아서 였습니다.
SpringBoot와는 다르게 Regacy 프로젝트 에서는 내부적으로 @ComponentScan을 통해서 자동으로 스프링빈을 찾아주는게 아니라
직접 web.xml에 빈으로 관리하겠다고 명시해주는 작업이 필요했습니다.
이미 구축된 Spring Regacy 프로젝트의 코어 부분을 변경해주는 작업은 처음이였기에,
또한 개인프로젝트로 항상 SpringBoot만 사용했기 때문에
레거시 프로젝트에서는 일일히 Bean을 명시해주어야 했다는 사실을 잊고 있었습니다.
그것도 모르고 '왜 애플리케이션 실행시점에 디버깅 포인트에 안걸리지?? Bean등록이 안되나? 설마 일일히 다 찍어봐야 하나...??' 했던.... 기억이 있습니다...ㅋㅋㅠㅠ
네. 여기서 실패했습니다....
스프링 버전도 잘 올렸겠다...!
자신있게 오픈소스 취약점 점검을 돌렸으나!!
여전히 Spring-core 버전에서 문제가 발생했습니다.
CVE-2016-1000027 라는 코드의 취약점으로
해당 취약점을 개선하는 해결 방안으로는
Spring-core 버전을 6으로 올리는것 외에는 없는것으로 보였습니다.
https://egovframe.go.kr/home/qainfo/qainfoRead.do?menuNo=69&qaId=QA_00000000000022453
https://stackoverflow.com/questions/74862694/cve-2016-1000027-fix-for-cve-2016-1000027-in-java-8-version
Spring4 -> Spring5 -> Spring6 -> SpringBoot3 으로 넘어가는 단계보다는
Spring4 -> Spring5 -> SpringBoot2 -> SpringBoot3 으로 넘어가는게 훨씬 간단하다고 생각했습니다.
SpringRegacy 프로젝트가 가지는 근본적인 취약점으로 생각합니다.
4->5 로 변경할때는
스프링버전변경 -> 의존성 충돌 확인 -> 다른 라이브러리 버전 변경 으로 비교적 간단(?) 했지만
5->6 으로 변경할때는
스프링버전변경 -> 변경된 의존성 패키지명 확인 -> 올바른 버전 찾기 -> 의존성 충돌 확인 -> 버전 변경 이라는 제법 힘든 의존성 변경과정이 필요했습니다.
반면에 SpringBoot는 dependency-management의 버전 변경만으로도
상당부분의 의존성 충돌이 해결됐기에, 훨씬 더 효율적인 업무가 가능할 것으로 판단되었습니다.
Spring6으로 올라오면서 JDK를 17로 올리는 것 뿐만 아니라,
톰켓의 요구사항도 10.1버전 이상으로 증가했습니다.
열심히 이것저것 건드려서 로컬환경을 변경하여 작업하는 중
갑자기 운영버전의 문제가 확인되었을 때 변경한 설정 때문에
운영버전을 로컬에서 즉시 띄워볼 수 없다던가 하는 문제가 발생했습니다.
반복되는 환경 출동문제를 겪으면서
변경하려는 소스는 내장톰켓을 사용하여 독립적으로 실행하고 싶었습니다.
document가 풍부하다는 이유는 특별한 기술을 채택함에 있어서 필요충분조건이라고 저는 생각합니다.
우선 chatgpt의 SpringRegacy6에 대한 답변이 만족스럽지 못했습니다.
특히 의존성 변경에 있어서 javax.servlet -> jakarta.sevlet의 변경점을 제대로 짚지 못했습니다.
또한 서칭을 수차례 한 결과 Spring6 버전부터는 대부분의 경우 SpringBoot를 사용하고,
SpringRegacy 형태를 유지하는 경우는 드문것으로 보였습니다.
이러한 환경에서 만약 마이그레이션 과정에서 예상하지 못한 문제점들을 마주한다면
해결이 쉽지 않을것 같았습니다.
SpringBoot2와 SpringBoot3은 두개 다 사이드프로젝트 등에서 익숙하게 사용하고 있었니다.
특히 SpringBoot2로 구성된 강의를 SpringBoot3으로 따라했던 경험도 있기 때문에
일단 SpringBoot로의 전환만 이루어지면
SpringBoot3으로의 마이그레이션은 손쉬운 일 일것이라고 생각했습니다.
sitemesh
JSP, Servlet 기반 애플리케이션에서 손쉽게 공통 레이아웃(템플릿) 을 적용해주는 라이브러리
각종 .xml파일로 구성된 설정들을 java config방식으로 변경하고
jsp를 기본 템플릿엔진으로 지정하여
SpringBoot를 통해서 서버 실행까지는 성공했습니다.
그러나
페이지는 어찌저찌 띄웠으나 css나 jquery등 기타 라이브러리가 의도대로 동작하지 않았습니다.
원인을 파악해보니 view단의 jsp 레이아웃인 sitemesh라이브러리에 있었습니다.
이곳에서 다른 파일이나 라이브러리를 미리 당겨오게 되어있었고
특히 이곳에서는 인증/인가에 관련된 기능 또한 복잡하게 엮여있었습니다.
한번도 다뤄보지 못한 라이브러리였고, 해당설정이 간단한 편도 아니었기에
해당 설정과 관련된 .xml 파일을 config 방식으로 변경하는것은 제게 상당히 큰 어려움이였습니다.

현재 직무상 풀스택(...이라쓰고 잡부라 읽는다....)개발을 하고있지만,
개인공부는 백엔드 위주로 하고있었으며
대체로 api만 구성하거나, 프론트엔드환경은 리액트로 진행하다보니
jsp나 해당 라이브러리에 대한 이해가 많이 낮았던 상태였었습니다.
단순히 기타 라이브러리를 당겨오거나
title/header/footer에 대한 정보만 있었더라면 어떻게든 시도해 볼만도 했었겠지만,
인증/인가와 관련된 처리까지 너무 깊숙히 엮여있어서 제 역량으로는 해결하기에 무리가 있었습니다.
그리고 저는 결국 sitemesh라이브러리를 이기지 못해버렸습니다...
SpringBoot로의 전환 실패 이후 Regacy형태를 유지하면서 Spring6으로의 전환을 시도해 보았으나,
Spring6과 sitemesh2버전의 패키지 충돌이 발생하여 여전히 sitemesh3.x 버전으로의 변경이 필요했고
3.x버전에서는 지원하지 않는 jstl 코드를 변경하는 과정에서 막혀버렸답니다....또르륵.....☆☆
사실 업무를 받은 계기가
퇴사를 한달 앞두고, 다른 새로운 업무를 깊게 들어가기가 힘든 상황에서
한번 해봐라. 되면 좋고! 안되면 어쩔수 없지... 정도의 느낌으로 혼자 맡게된 도전적인 업무였습니다.
업무가 혼자가 아니라 jsp를 잘 아는 팀원이 함께 했었더라면,
시간이 더 있었더라면... 하는 아쉬움이 많이 남습니다.
1 . 철저한 브랜치 분리의 중요성
상남자특) 마스터브랜치만 씀
마이그레이션에 앞서 브랜치를 분리하는것이 아닌,
별도의 리포지토리를 구성하는 식으로 작업을 진행했습니다.
마이그레이션 중 운영버전에 대한 수정 요구사항이 수시로 발생하였고.
이로인해 운영코드의 변경점을 따로 기록해두었다가
변경 하려는 코드에 매번 함께 반영하는 과정이 필요했습니다.
이러한 업무는 개발자에 따라서 누락될 가능성이 있고,
똑같은 코드를 두번 작성하는것이 상당히 귀찮은 일로 돌아왔습니다.
이 과정에서 브랜치 관리를 적절하게 할 수 있다면
각각의 브랜치에서 작업하고, 마지막에 한번의 병합 과정을 통해서 일을 훨씬 능률적으로 할 수 있을것 같다는 생각이 들었습니다.2. 기술부채는 결국 복리로 돌아온다...
실패한자의 변명 이겠지만, 뷰 템플릿엔진이 jsp가 아닌
React와 같은 별도의 was 혹은 다른 템플릿엔진 이였다면.... 하는 아쉬움이 많이 남습니다.
특히나 마이그레이션을 진행하면서, Spring5까지는 jsp를 어떻게든 사용했어도
Spring6에서는 jsp사용이 더욱 더 어려워짐을 느꼈습니다.
여기에 더 큰 문제는 현재 사용하는 grid가 jsp가 아니면 호환되기 어렵다는것과
sitemesh역시 jsp에서만 사용되는 라이브러리였습니다.
회사입장에서도 어쨌든 멀쩡히 돌아가는 코드를 굳이 리팩토링 할 이유가 없으며
개발자들의 jsp에 대한 익숙함, 개발능률 등을 고려했을때 jsp를 유지한 것으로 보입니다만,
이는 결국 Spring6으로 전환에 실패하는 요인이 되어버렸습니다.
업데이트가 중지됐어? 근데? 멀쩡히 돌아가는 프로그램을 뭐하러 바꿔? 그냥 쓰면 안돼?라는 질문에 대해서
꾸준히 업데이트 하지 않으면 나중에 프로그램 전체 구조를 변경해야 되는 불상사가 생길 수 있다. 라는 나름의 답변을 할 수 있게 되었습니다.3. 역시 계속 공부하지 않으면 안되는 직업
비록 마이그레이션에는 실패했지만
그래도 자신있게 말할수 있는 부분은
서버쪽 web.xml, servlet-context.xml 등과 각종 설정부분을 자바코드로 온전히 녹여내어 SpringBoot2, SpringRegacy6 버전에서 각각 서버 실행은 성공했습니다.
만약 회사에서 SpringBoot를 사용하지 않고, 예전기술을 사용한다고 해서
최신 기술에 대한 개인공부를 게을리 했다면 성공하기 어려웠을 성과라고 자부합니다.
특히 SpringBoot2로 작성된 강의를 SpringBoot3으로 따라한다던가,
SpringSecurity의websecurityconfigureradapter으로 구성된 예제를SecurityFilterChain으로 따라했던게 이번 업무에 큰 도움이 되었습니다.
당장 사내에서 사용하지 않는다고 다른 기술들에 대해 무관심 하지않고
귀를 열고 항상 열린마음으로 내공을 쌓아야 한다는 생각을 다시금 되새기는 계기가 되었습니다.
저는 실패했습니다.
하지만 왜 실패했는지, 어떤 부분이 어려웠는지에 대한 원인을 팀과 공유했습니다.
jsp환경에서의 어려움(게다가 grid등의 문제로 탈 jsp의 어려움), spring6 문서부족, .do로 끝나는 url 맵핑 등등...
이후 회사에서 결국 Spring6으로 전환에 다시금 시도해서 성공할지,
아니면 이후 미팅에서 적당히 타협을 할 지는 모르겠습니다.
다만 제가 실패한 이후
회사 선배가 새로운 개발건을 대비하여 SpringBoot3 + ThymeLeaf 구조로 프로젝트를 새로 구성하기 시작했습니다.
이때 과감히 프로젝트를 처음부터 구성한 점, jsp를 포기한점, 새로운 grid를 도입하기 시작한 점 등 을 보았을 때
의사선택에 있어서 제가 공유한 어려운 점이 충분히 반영되었다고 생각합니다.
재미난 일을 열심히 몰입해서 도전해 보았고,
아쉽게도 원하는 성취는 이루지 못했지만, 그 과정이 헛된 시간은 아니였다는 마음가짐을 가지고
항상 도전하며 앞으로도 재미난 업무를 더 더 해보고 싶습니다.
읽어주셔서 감사합니다.