ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 3 마이그레이션
    글또 2024. 4. 14. 12:30

    0. 들어가며

    안녕하세요.
    이번에는 저희 서비스가 spring 버전을 올리면서 겪었던 문제들에 대해서 이야기해보려고 합니다.
     
     
    글의 순서는 다음과 같습니다.

    1. 버전 업데이트의 이유
    2. 버전 업데이트
    3. 대표적인 변경점
    4. 마무리

     
     

    1. 버전 업데이트의 이유 : Spring Security의 문제

     

     
    Spring Security 어노테이션을 사용한 권한 검사가 의도한 대로 동작하지 않았습니다.

    컨트롤러에 적용한 전역 어노테이션과 메서드에 적용한 커스텀 권한 검사(custom attribute)가 함께 작동하지 않고, 대신 커스텀 권한 검사만 작동했습니다.

    두 어노테이션이 동일한 것이었다면 더 구체적인 범위의 어노테이션이 우선적으로 적용되어야 할 것이지만, 두 어노테이션이 서로 다르기 때문에 의아했습니다.
     
     
    찾아보니 Spring Security 5.6 미만 버전에서는 메소드 레벨의 권한 검사 어노테이션을 중복으로 사용할 수 없었습니다.
     

    2024.02.03 spring security Authorization Manager Method Security

    5.6 버전 이전에는 Secured와 PreAuthorize를 동시에 사용할 수 없었지만 5.6 이후에는 동시에 사용할 수 있게 되었음. 이유는 다음과 같다. 5.6이전에는 MethodSecurityInterceptor가 해당 메소드의 권한을 검증

    gisungcu.tistory.com

     

    어노테이션 중복을 사용하고 싶다면 몇 가지 방법이 있습니다.

    1. @PreAuthorize 어노테이션과 SpEL을 사용: Spring Security에서 제공하는 @PreAuthorize 어노테이션과 Spring Expression Language (SpEL)을 사용하여 권한 검사를 수행할 수 있습니다. 이를 통해 여러 권한 검사 로직을 하나의 메서드에 적용할 수 있습니다.
    2. 커스텀한 Attribute에 여러 검증 로직 추가: 직접 커스텀한 Attribute에 여러 검증 로직을 추가하여 사용할 수 있습니다.
    3. Spring Security의 버전을 5.6 이상으로 올리기: Spring Security 5.6 버전부터는 메소드 레벨의 권한 검사 어노테이션을 중복으로 사용할 수 있게 되었습니다. 이를 활용하여 중복 어노테이션을 적용할 수 있습니다.

     
    저희는 1번은 변경점이 많고 2번은 기존 설계 의도를 벗어나는 것이라고 판단했습니다
    그래서 서비스 오픈 전에 빠르게 버전을 올리는 것으로 결정했습니다.

     

    두 번째 선택

     
    저희의 spring 버전은 2.4.5를 사용 중이었기 때문에 spring security를 5.6으로 손쉽게 올릴 수 있었습니다.
    그러나 업그레이드 후에는 secured 어노테이션의 권한 검사를 담당하는 secured Authentication Manager가 권한 계층을 기반으로 검사하지 않는 문제가 발생했습니다.
    찾아보니 secured 어노테이션의 권한 계층 검사는 spring security 6.1부터 지원했습니다.
     
    해결 방법

    1. 권한 검사 구현체인 secured Authentication Manager를 오버라이딩하기
    2. spring security를 6.1 이상으로 올리기

    저희는 하위 버전을 사용하면서 상위 버전에서 제공하는 기능을 구현할 필요가 없다고 판단하여 상위 버전에서 제공하는 기능을 활용하기로 결정했습니다.
    먼저, spring security 6.1은 spring 6.0.9 (spring boot 3.1.0)부터 지원하기 때문에, spring boot 버전부터 올리는 작업을 진행해야 합니다.
     
     

    2. 버전 업데이트

     

    spring security maven repository

     
     
    라이브러리가 어떤 버전을 지원하는지, 그리고 spring boot가 어떤 spring 버전과 함께 사용되는지 등의 정보가 Maven Repository에 제공됩니다.
    저희는 spring의 버전을 단번에 3.x버전까지 올리기보단 2.x버전의 가장 마지막인 2.7까지 업데이트를 하고  3.x버전으로 가는 것이 에러 잡기가 쉬울 것이라고 판단했습니다.
     
     

    2.4.2 → 2.7.14 버전 업데이트

    버전을 올리기 전에 릴리즈 노트를 참고해 볼 수 있습니다.
    2.5 Release note
    2.6 Release note
    2.7 Release note

     
     
    저희는 현재 사용 중인 spring boot 버전이 2.4.2이며, 회사에서 제공되는 프로젝트 템플릿의 버전입니다.
    이번 버전 업데이트에서는 구현 변경으로 인한 오류들을 수정하고, security 설정 방법을 업데이트했습니다.
    변경된 security 아키텍처를 반영하는 것은 6.1 버전까지 하고 수정할 것입니다.
     
     
    업데이트한 라이브러리 목록

    더보기
    1. spring security 버전 업 (5.4 → 5.7)
      • 설정 구조 변경에 따라서 bean등록 구조로 변경
    2. gradle 버전 업 (6.4.1 → 6.8.2)
      • spring boot 버전을 실행 시킬 수 있는 gradle로 수정했음
    3. assertj 버전 업 (→ 3.20.2)
    4. 코드 수정

     

    2.7.14 → 3.1.2 버전 업데이트

     

    버전을 올리기 전에 릴리즈 노트를 참고해 볼 수 있습니다.
    3.0 Release note

     
    3.x로 올라가면서 많은 변화가 있었습니다.
    이 과정에서 많은 에러와 라이브러리를 변경했고, 회사 내부 라이브러리 충돌로 인해 재배포를 진행하기도 했습니다.
    이에 대해 3가지 수정사항을 살펴보겠습니다.
     
     
    업데이트한 라이브러리 목록

    더보기
    1. 자바17로 변경
      • 사유: spring boot 3.x가 java 17을 최소버전으로 함
    2. kotlin-gradle-plugin 버전 업 (1.4.30 → 1.6.21)
      • 설명: 코틀린 컴파일 플러그인
      • 사유: 지원하는 JVM 버전 충족
    3. gradle 버전 업 (6.8.2 → 7.6.2 → 8.6)
    4. gradle-git-properties 버전 업 (2.2.4 → 2.3.1)
      • 설명: Git 리포지토리와 관련된 정보를 빌드 자동화에 사용할 수 있도록 도와주는 플러그인
      • 사유: 기존 버전에서 jdk 16이상일 경우 빌드 에러 발생
    5. jakarta.servlet 버전 업 (4.0.3 → 5.0.0 → 6.0.0)
    6. jakarta annotation 버전 업 (1.3.5 → 2.1.0)
      • 사유: undertow에서 사용하는 jakarta 버전의 변경
    7. aspectj weaving플러그인 변경 (gradle-aspectj-binary → aspectj-plugin)
    8. undertow 버전 업 (2.1.1 → 2.3.7)
    9. mybatis-spring 버전 업 (2.0.4 → 3.0.3)
      • 사유: java17에서의 지원 X
    10. mybatis-spring-boot-autoconfigure 버전 업 (2.1.2 → 3.0.3)
      • 사유: java17에서의 지원 X
    11. mybatis 버전 업 (3.5.4 → 3.5.14)
      • 사유: 하는 김에 버전 업
    12. spring kafka 버전 업 (2.8.x → ~ 3.0.8)
    13. pebble template 버전 업 ( pebble-spring5~ → pebble-spring6:3.2.0)
    14. kotlin-stdlib, kotlin-reflect 버전 업 (1.4.21 → 1.9.20)
      • 사유: jackson-module-kotlin에서 버전 상위의 kotlin 메소드를 호출함
    15. jackson-databind @Transient 오류
    16. 사내 라이브러리 validation 버전 업 (1.0.1 → 1.0.8)
      • 사유: spring boot가 3.x대로 넘어가면서 상위 버전의 jakarta를 사용함
      • 문제
      • jakarta validation 버전 업 (2.0.2 → 3.0.2)
    17. 사내 라이브러리.argon 버전 업 (1.0.1 → 1.0.9)
    18. slf4j 관련 버전 업 (1.7.x →2.x)
      • 사유: spring 버전 업하면서 같이 버전 업
      • slf4j-api 버전 업 (1.7.36 → 2.20.0)
      • log4j-slf4j-impl 버전 업 (2.17.2 → 2.20.0)
      • log4j-core 버전 업 (2.17.2 →2.20.0)
      • log4j-jul 버전 업 (2.17.2 → 2.20.0)
    19. slf4j-jdk14 버전 추가 (2.0.7)
    20. M1 로컬 디펜던시 추가
    21. application properties 수정
      • spring.datasource.initialization-mode=NEVER → spring.sql.init.mode=NEVER
    22. SecurityConfig.kt 수정
      • 설정 방법 변경 반영
      • 메소드 시큐리티 아키텍쳐 변경 반영
        • FilterSecurityInterceptor → AuthorizationFilter
        • MethodSecurityInterceptor →AuthorizationManagerBeforeMethodInterceptor
    1.  

     
     

    3. 변경 사항으로는...

    변경 사항으로 인해 발생한 문제가 있었습니다.

     

    3-1. Java EE에서 Jakarta EE로

     

     
    Java 관리 주체가 오라클에서 이클립스 재단으로 옮겨가면서 상표권 문제로 네임스페이스가 변경되었습니다.
    이에 따라 javax에서 jakarta로 패키지 구조가 변경되어 코드 레벨에서 수정이 필요했습니다.
     
    이 변화는 단순히 내부 코드뿐 아니라 연관된 라이브러리들도 jakarta를 사용하는 버전으로 업데이트해야 했습니다.
    다행히 외부 라이브러리들은 대부분 jakarta를 사용하는 버전을 내놓았기 때문에 업데이트는 비교적 수월했습니다.
    그러나 만약 jakarta 업데이트 버전이 없었다면 재정의하거나 새로운 구현을 해야 했을 것입니다.
     
    또한, 회사 내부 라이브러리도 jakarta가 아닌 javax를 사용하고 있었는데, 이를 jakarta로 업데이트하고 재배포함으로써 문제를 해결할 수 있었습니다.
     
     
     

    3-2. Argon2-jvm, JNA, Jdk17, M1 문제 

     

    GitHub - phxql/argon2-jvm: Argon2 Binding for the JVM

    Argon2 Binding for the JVM. Contribute to phxql/argon2-jvm development by creating an account on GitHub.

    github.com

     
    Argon2-jvm은 암호화 라이브러리이며, JNA를 통해 Argon2 C 라이브러리와 상호 작용합니다.
    내부에서 해당 알고리즘을 사용하는 부분이 있었는데 Jdk17로 업데이트하면서 UnsatisfiedLinkError가 발생하였습니다.
     
    해당 에러를 따라가다보니 M1을 사용 중인 경우 JNA 버전이 5.8 이하일 때, 내부적으로 M1(aarch64) 아키텍처를 지원하지 않아서 해당 패키지를 찾을 수 없다는 에러가 발생합니다.
    문제 해결은 M1의 패키지 구조를 지원하는 Jna:5.8, Argon2-jvm:2.11로 업데이트하면 됩니다. 
     
    궁금한점은 Jdk11을 썼을 때는 CPU 종류에 따른 문제없이 잘 쓰다가 Jdk17로 업데이트했을 때 갑자기 발생했냐 였습니다. 
     
     

    jdk11

    디버깅을 하다보니 흥미로운 사실을 발견했습니다.
    Jdk11에서 CPU 아키텍처를 얻는 코드 부분에서는 M1(aarch64)가 아닌 x86_64로 나왔습니다.
    그러다 Jdk17로 디버깅을 하면 올바르게 aarch64로 표시되었습니다.
     
    그래서 Jdk11에서는 M1과 관련된 문제가 발생하지 않았고 Jdk17로 업데이트하면서 M1을 정상적으로 지원하게 되면서 Jna와 Argon2-jvm에서 에러가 발생한 것입니다.
     
    그럼 왜 Jdk11은 M1을 x86_64로 인식한 것 일까요?
    Jdk11을 사용할 때는 M1을 공식적으로 지원하지 않는 Oracle Open JDK를 사용했기 때문에 Rosetta에 의해 실행되어 "os.arch" 속성을 통해 얻은 결과가 x86_64로 표시되었습니다.
    따라서 Jdk11을 사용할 때도 M1을 지원하는 JDK를 사용했다면 동일하게 오류가 발생할 것입니다.
     

    M1 프로그램 활성 상태

    Jdk11과 Jdk17로 프로그램을 실행시켰을 때, 호환되는 CPU 아키텍처가 서로 다르게 표시된다는 것을 볼 수 있습니다.
    이 문제는 해결이 비교적 쉬웠지만, 처음에는 머리로는 이해되지 않아서 원인을 찾는 데 시간이 걸렸습니다.
     
     
     
     

    3-3. Spring security 아키텍쳐 변경

    spring security 5.6부터는 메소드 인증과 인가 아키텍처에 변화가 있습니다.
    이전 버전의 아키텍처와 실행 과정은 다음과 같습니다.
     
    spring security 5.6 미만

    5.6 이전

     
    Method security
     

    1. Method Security Interceptor는 method가 가지고 있는 attributes(MethodSecurityMetadataSource의 'Attributes')를 DelegatingMethodSecurityMetadataSource에게 요청합니다.
      • attributes는 DelegatingMethodSecurityMetadataSource로부터 받아옵니다.
      • DelegatingMethodSecurityMetadataSource는 실행되는 메소드의 모든 attributes중 첫번째 것을 캐싱합니다.
        • 우선 순위는GlobalMethodSecurityConfiguration가 설정합니다. custom attribute > prepost > secured > Jsr250
        • secured, PreAuthorize 어노테이션을 같이 사용할 수 없는 이유는 여기에 있습니다.
    2. Interceptor는 AccessDecisionManager 에게 투표 처리를 요청합니다.
    3. AccessDecisionManager  (AffirmativeBased)는 voter들에게 투표를 받고 결과를 처리합니다.
      • voter는 자신이 처리(supports) 할 수 있는지 attribute의 타입을 보고 결정합니다.
        1. (CustomAttribute, PreInvocationAttribute, PostInvocationExpressionAttribute)
      • 대부분의 경우, 투표자는 1명입니다.
        • PrePostAnnotationSecurityMetadataSource는 속성을 2가지 가지고 있기 때문에 여러 투표자가 될 수 있습니다.

     
    spring security 5.6 이상

     

    FilterSecurityInterceptor, MethodSecurityInterceptor deprecated되고 AuthorizationManagerInterceptor가 등장했습니다.

    아키텍처가 변경되어서 이제는 모든
    AuthorizationManager를 통과해야만 메소드를 실행할 수 있습니다.
    이제 AuthBeforeInterceptor가 있어서 @PreAuthorize, @Secured 어노테이션 등을 하나씩 검증하는 구현체를 담당합니다.
    따라서 모든 어노테이션들을 통과해야만 메소드가 실행됩니다.
     

    IP White List: 보안 취약점과 강화 방안

    이전 글 spring security white list 어노테이션 만들기 안녕하세요. 이번에는 spring security의 PreAuthorize를 커스텀한 이야기를 해보려 합니다. 목표는 인증인가 없이도 white list에 등록된 ip라면 자원에 접

    gisungcu.tistory.com

     
     

    4. 마치며

    버전 업은 까다로운 작업이라는 것을 알고 있었지만 직접 경험하고 나니 그 말이 맞았습니다.
    다행히 회사 업무가 조금 여유로웠던 시기여서 버전 업에 집중할 수 있었습니다.
    이를 통해 빌드 스크립트도 정리할 수 있었고, 런타임에 발생하는 에러를 해결하기 위해 관련 라이브러리를 디버깅하는 등 좋은 경험을 얻을 수 있었습니다.
    비즈니스 요구사항에서 벗어나 컴퓨터와만 씨름하는 것이 더욱 재밌었습니다.
     
    +

    • gradle 버전 충돌

     
     

    댓글

Designed by Tistory.