팀 프로젝트를 진행하는 중 springdoc을 추가하면서 springBoot의 버전이 3인 것을 확인하였다. 부트 버전이 3인 것이 우리 프로젝트에 도움이 되지 않는다고 생각하여 이 점을 팀원들에게 어필하고 변경하는 과정을 겪었고 이 과정에서 발생한 오류나 공부하는 방식 등을 기록하며 돌아보려고 한다.
팀 프로젝트를 진행 중에 팀장님이 AWS를 통해서 서버를 올리는 작업까지 해주셨는데 이 과정에서 조금 문제가 생긴 것이 스프링 부트의 3버전을 사용하셨다. 사실 문제라고 하기는 그렇지만 워낙 최신의 버전이라 많은 Open API를 사용하는 과정에서 많은 변화가 있는 것이 문제가 되었다.
이 외에도 많은 문제점이 있기에 3버전이 아닌 2버전으로 버전을 옮기는 과정을 제안했고 팀원 분들의 동의를 얻어서 2버전으로 옮기려고 한다.
사실 먼저 해결하기 보다 일단 springboot2와 springboot3의 변경된 부분과 변경하기 위한 과정들을 살펴 보았다. 스프링 부트 3 준비, 마이그레이션 방법
기본이 자바 17이다. 사실 자바 11버전을 주로 공부했기에 17버전으로 사용하더라도 별로 기능을 사용하지 못하기는 한다. 그래서 사실 큰 문제는 없다. 오히려 여러가지 호환성 문제에서 17이 더 좋은 측면이 많은 것 같다. Record 기능에 대한 관심이 있기에 공부를 해볼 생각이다.
일부 키가 변경되었는데 따라서 Maven이나 Gradle에서 라이브러리를 입력하는 과정에서 약간의 변화가 생겼다.
가장 익숙하지 않은 곳으로 Jakarta로 변경되고 있다는 사실만 알고 있을 뿐이지 사용을 해본적이 없어서 내가 Springboot3이 아닌 2버전으로 다시 낮추는 과정을 팀원들에게 제안한 이유다.
이전에 Validation관련된 Hibernate를 사용하는 과정에서 이런 저런 문제점을 겪었었는데 이번 프로젝트에도 Jpa를 사용하기에 버전을 내릴 것을 제안했다.
스프링 시큐리티, 하이버네이트, 스프링 배치 등 스프링 생태계에 전반적인 변화가 있었기에 사용해본 적이 없이 그냥 스프링 부트라는 이름으로 사용하기에는 조금 버거운 감이 없지 않은 것 같다.
사실 프로젝트 초반에 2버전으로 변환한 것이기에 큰 변화는 없었다. 프로젝트가 어느정도 진행된 시점에서 2버전으로 전환하려고 했다면 하나의 큰 작업이 되었을 것 같아서 다행이라고 생각한다.
일단 gradle에서 부트를 변환했다.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.11' // '3.1.0' -> '2.7.11'
id 'io.spring.dependency-management' version '1.1.0'
}
...
그 후에 프로젝트를 일단 실행해서 오류를 찾기로 했다. 사실 프로젝트가 올라가기도 전에 IntelliJ에서 오류를 찾아줬는데
@Bean
public WebSecurityCustomizer webSecurityCustomizer(){
log.info("-------------web configure-------------");
// return (web) -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
return (web) -> web.ignoring().requestMatchers(
// "/**",
"/swagger-ui/**",
"/api-docs/**",
"/test"
);
// "/**" 임시로 모든 보안 해제시 셋팅!
}
에서 requestMatchers를 변경해달라는 것이었다. 여기서 requestMatchers를 antMatchers로 변경하면 문제없이 작동하게 된다. 확실히 변경점이 적은 것이 다행이라고 생각한다.
requsetMatchers는 처음 보는 내용이었기에 약간 찾아보았다. 그리고 스프링 부트 3에서 사용한다는 것은 지금 사용중인 스프링 시큐리티의 더 높은 버전이기에 내가 앞으로 사용하게 될 기능이라고 생각하여 찾아보았다.
일단 부트2 에서 부트3 으로 변경되면서 스프링 시큐리티 또한 6버전으로 설정이 되어있다. 일단 스프링 시큐리티 6버전의 전체적인 내용을 이해하기에는 조금 벅차기에 requsetMatchers에 집중했다.
일단 requestMatchers가 스프링 시큐리티 5버전에서 적용되었기에 이에 맞는 스프링 시큐리티 5의 공식 문서에서 requestMatcher를 찾았다.
Use the new requestMatchers methods
In Spring Security 5.8, the antMatchers, mvcMatchers, and regexMatchers methods were deprecated in favor of new requestMatchers methods.
The new requestMatchers methods were added to authorizeHttpRequests, authorizeRequests, CSRF configuration, WebSecurityCustomizer and any other places that had the specialized RequestMatcher methods. The deprecated methods are removed in Spring Security 6.
These new methods have more secure defaults since they choose the most appropriate RequestMatcher implementation for your application. In summary, the new methods choose the MvcRequestMatcher implementation if your application has Spring MVC in the classpath, falling back to the AntPathRequestMatcher implementation if Spring MVC is not present (aligning the behavior with the Kotlin equivalent methods).
To start using the new methods, you can replace the deprecated methods with the new ones. For example, the following application configuration:
일단 대부분의 내용이 대체된다. 바뀌어야한다. 등의 기존의 작성했던 방식을 바꿔야 한다는 내용이 많다. 그 중에서 이전에 사용했던 antMatchers 부분을 바꿔야 한다고 적혀있고 팀장님이 이 내용을 따라서 requestMatchers 메서드를 사용하였고 내가 버전을 내리면서 다시 antMatchers로 변경하였다.
이 외에도 mvcMatchers나 regexMatchers 등의 다양한 Matchers를 하나의 requestMatchers로 표현할 수 있어 개발자들에게 더 좋은 방식을 제공한다.
사실 이 공식 문서 전체를 읽고 해석하는데에는 문제가 있어 가장 큰 장점을 Chat GPT에 물어 봤다.
Let me know what you like using requestMatchers
좋아진 점을 알려달라는 질문을 했고
As an AI language model, I do not have personal preferences or feelings. However, I can tell you that requestMatchers is a useful feature in Spring Security as it allows for more fine-grained control over which requests are intercepted and processed by the security filter chain. With requestMatchers, you can define more specific criteria for matching requests, such as matching on HTTP methods, request parameters, headers, cookies, and more.
This feature can be particularly helpful when dealing with complex security requirements and when you want to selectively apply security measures to only certain endpoints or URLs. It can also help improve the performance of your application by reducing the number of requests that need to be processed by the security filter chain.
However, it is important to note that requestMatchers is only available in Spring Security 5 and above, and is not supported in earlier versions of Spring Security. If you are using an earlier version of Spring Security, you may need to use a different approach to achieve similar functionality.
Chat GPT는 스프링 시큐리티의 requestMatchers를 사용하면서 가장 좋아진 점으로 세밀한 제어가 가능한 점을 짚었다. 그리고 처리해야하는 요청의 수를 줄여 성능 개선 또한 이루어 냈다고 말한다. 이에 대한 예시를 물어 보았고 다음과 같은 코드를 주었다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // permit access to admin endpoints only for users with ADMIN role
.antMatchers("/css/**", "/js/**", "/images/**").permitAll() // permit access to static resources
.antMatchers("/h2-console/**").permitAll() // permit access to H2 console
.anyRequest().authenticated() // any other request must be authenticated
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // permit access to static resources
.requestMatchers(PathRequest.toH2Console()).permitAll() // permit access to H2 console
.anyRequest().authenticated() // require authentication for all other requests
.and()
.formLogin() // enable form-based login
.and()
.logout(); // enable logout
}
}
가장 큰 차이점은 이제 더 이상 모든 경로를 따옴포"" 안에 작성할 필요가 없다는 것이다. 이제는 toStaticResources()나 toH2Console()과 같은 메서드로 작업할 수 있어졌다. 이를 통해서 더 이상 잘못 입력하는 문제나 **처럼 모든 경로라는 의미의 애매한 내용을 사용할 일이 없어졌다. 따라서 모든 경로의 요청을 보내는 것이 아닌 H2의 정확한 엔드포인트를 적용하기에 필터의 단계가 줄어들어 성능을 향상 시킬 수 있다.
분명 좋은 기능인 것은 사실이나 현재 우리가 사용하는 다른 라이브러리나 API등을 위해서 포기해야하는 기능이라고 생각한다. 최신의 기술을 가져와 적용함으로써 기능 향상을 노리는 것은 좋으나 그에 따른 포기해야할 다른 기술이 있다는 것을 명심하자.
일단 처음에는 springdoc을 스프링 부트 3버전에 맞게 해결하는 과정을 시도하려고 했다.
springdoc_v2 springdoc의 공식 홈페이지에도 스프링 부트 3버전을 사용할 경우 적용하는 법이 나와있다. 처음에는 이 문서를 참고해 springdoc의 2.1.0 버전을 적용하여 Swagger UI를 구현하려고 했다.
일단 반 정도 구현하는 과정에서 생각한 것이 '이번 프로젝트를 하면서 많은 외부 Open API와 라이브러리를 사용할 것인데 괜찮나?'라는 걱정이 커져갔다. 3버전으로 업데이트 하는 과정이 상세하게 적힌 공식 문서나 그래도 나름 괜찮은 글이 적힌 블로그가 있었지만 양의 차이가 엄청났다. 스프링의 가장 큰 장점 중의 하나가 다수의 사람들이 이용하기에 존재하는 큰 커뮤니티였는데 3버전을 이용할 경우 이런 커뮤니티를 이용하지 못하는 점이 2버전으로 내려가는 데 큰 역할을 했다.
일단 Maven Repository에서 가장 많이 사용하는 springdoc 디펜던시를 찾아서 추가했다.
// https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.9'
그 후에 서버를 작동하고 http://localhost:8080/swagger-ui/index.html에 접속을 했다.
이전에 팀장님이 swagger의 uri를 스프링 시큐리티에서 권한 설정을 마쳐 줬기에 단순하게 dependcy만 추가해도 잘 작동하였다.
@Log4j2
@RequiredArgsConstructor
@EnableMethodSecurity
@EnableWebSecurity
@Configuration
public class SecurityConfig {
...
// 필터에서 제외시킬 url 등록
@Bean
public WebSecurityCustomizer webSecurityCustomizer(){
log.info("-------------web configure-------------");
// return (web) -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
return (web) -> web.ignoring().antMatchers(
// "/**" // 임시로 모든 보안 해제시 셋팅!
"/v3/api-docs/**",
"/swagger-ui/**",
"/test"
);
}
}
그런데 Swagger에 접속을 해보니 Failed to load remote configuration.라면서 api들을 Swagger에서 읽어오지를 못하고 있다.
왜 읽어오지 못하는 지 개발자 도구의 네트워크를 확인해 보니
403 에러로 즉 권한에 문제가 있기에 불러오지 못하고 있었다. 이를 해결하기 위해 시큐리티 설정에 "/api-docs/**"을 추가해 권한을 부여하였다.
그랬더니 이번에는 404에러가 나왔다. 못 찾는다는 의미였다. 이를 해겨하기 위해서는 springdoc의 추가 설정이 필요했고 property 파일에 설정을 추가해서 문제를 해결했다.
application.properties
# springdoc Swagger 설정
# swagger-ui로 접속
springdoc.packages-to-scan=project.finalproject1backend
springdoc.default-consumes-media-type=application/json;charset=UTF-8
springdoc.default-produces-media-type=application/json;charset=UTF-8
springdoc.swagger-ui.path=/swagger-ui
springdoc.swagger-ui.tags-sorter=alpha
springdoc.swagger-ui.operations-sorter=alpha
springdoc.api-docs.path=/api-docs/json
springdoc.api-docs.groups.enabled=true
springdoc.cache.disabled=true
프로젝트를 시작하면서 빠르게 스프링 부트 3버전을 사용함에 있어 생기는 또는 생길 여러 문제를 미리 발견한 점이 다행이라고 생각한다. 스프링 부트 3버전이 점점 발전하면서 IDE인 IntelliJ의 기본 버전이 3버전으로 되어 있어 나도 여러번 3버전으로 시작한 적이 있었다. 그러면서 Validation이나 하이버네이트 쪽을 쓰면서 뭔가 알 수 없는 에러나 정말 이해가 안가는 상황이 몇 번 있었고 이를 통해서 버전을 잘 설정하는 것이 중요하다고 배웠기에 발견할 수 있었다고 생각한다.
내가 부트 버전을 잘못 선택해서 겪은 문제들을 사실 클릭 한번, 프로젝트 만들면서 관심 한 번 주지 않은 것으로 시간을 날렸다고 많이 생각했었는데 이번 기회에 이런 에러를 만난 경험을 살려서 기존의 실수들이 다 의미가 있었다고 생각한다.
또한 프로젝트를 시작하는 단계에서 어떤 버전을 사용하고 어떤 도구를 사용할 지 정하는 것이 굉장히 중요하다는 생각을 가지게 되었다. 나 혼자 진행하는 프로젝트나 작업에서는 전혀 고려하지 않은 문제로 팀 프로젝트를 진행하는 과정에서 고려해야할 중요한 사항이라는 것을 배우게 되었다.
공부를 하고 작업을 하면서 이제는 에러 해결하는 과정이 상당히 매끄러워진 것을 느낀다. 기존에는 문제가 생겼다? 그러면 일단 문제 에러 로그를 복사해서 구글에 검색해서 어떻게든 해결 방법만 찾으려고 노력했다면 이제는 문제의 원인 -> 공부(공식 문서, 블로그, Chat GPT) -> 내가 생각하는 해결 방법 (-> 디버깅) -> 문제 해결의 순서로 내가 이 문제가 발생한 이유를 명확하게 알고 해결하는 방식을 습득하고 적용하고 있다. 그리고 이런 과정에서 이번 에러와 조금은 결이 다르지만 그래도 배우면 좋은 것, 배워야 할 것 등 여러가지 많은 정보를 배우고 있다. 이번에는 스프링 시큐리티의 5버전의 requestMatchers가 있다.
또한 지식이 많이 늘어서 이제는 문제가 발생하면 크롬의 개발자 도구를 사용하여 무슨 에러가 나는지 에러 로그가 어떤 의미인지를 파악하고 이를 이용하여 스프링 시큐리티에 적용하는 나의 지식으로 문제를 해결하는 능력이 생겼다고 생각한다. 모든 문제는 아니지만 그래도 작은 문제들은 의존하지 않고도 문제를 해결할 수 있는 능력을 가지게 된 것이다.
검색에 나오지 않거나 조금만 내가 원하는 검색 내용이 나오지 않으면 걱정을 태산같이 하던 내가 바뀌게 된 것을 느끼게 되었다.
사실 단순하게 springboot3을 2버전으로 낮추자고 했다면 내 의견에 힘이 없었을 것이고 팀원들도 받아들이기 힘들었을 거라고 생각한다. 경험과 지식을 토대로 팀원들에게 springboot3이 아닌 2버전이 가지는 장점(더 많은 정보, 지원하는 라이브러리나 Open API)과 단점(Validation, 하이버네이트와 같은 프로젝트에서 사용할 라이브러리나 API들의 사용법 변경)을 기반으로 의견을 제시 했기에 제안이 받아들여졌다고 생가한다.
공부를 열심히 했기에 받아들여졌다.라는 경험이 값지게 느껴졌다.
어찌보면 간단한 오류 해결 과정이었지만 이번 오류를 해결하면서 어떻게 해서든 구글 검색이나 블로그를 통해서 해결 과정만을 찾던 방식을 완전하게 벗어난 것을 느꼈다. 이제는 구글에 검색을 하더라도 해결 방식을 찾는 것이 아닌 왜 오류가 발생했는지를 찾게 되었다. 그리고 문제를 공식 문서를 통해서 이해하는 과정을 꼭 가지고 이런 과정에서 Chat GPT와 같은 인공 지능 또한 활용해 학습의 속도를 올리는 방법도 익히게 되었다.
지식의 양을 늘려서 문제를 해결한다.라는 간단한 말을 현실로 옮기기 위해 계속해서 노력할 것이다.
도움 받은 곳
https://velog.io/@devmin/springdoc
https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#use-new-security-matchers
https://stackoverflow.com/questions/70906081/springboot-swagger3-failed-to-load-remote-configuration
https://www.baeldung.com/spring-boot-3-migration
https://nahwasa.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-30%EC%9D%B4%EC%83%81-Spring-Security-%EA%B8%B0%EB%B3%B8-%EC%84%B8%ED%8C%85-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0
https://revf.tistory.com/260
https://springdoc.org/
https://springdoc.org/v2/
https://chat.openai.com/