sggnology
하늘속에서IT
sggnology
전체 방문자
오늘
어제
  • 분류 전체보기 (83)
    • Algorithm (31)
      • Programmers (27)
      • Baekjoon (4)
    • WIKI (4)
      • VirtualBox (1)
      • Power Toys (1)
    • NodeJS (4)
      • nvm (1)
      • React (1)
      • Vue (1)
    • Dev Language (3)
      • Java (2)
      • Kotlin (1)
    • Spring Boot (17)
      • Gradle (1)
      • JPA (3)
    • DB (4)
      • MariaDB (3)
      • Redis (0)
    • Android (6)
      • Debug (3)
    • Nginx (3)
      • Debug (1)
    • Intellij (0)
    • Network (1)
    • Git (2)
      • GitHub (2)
    • Chrome Extension (0)
    • ETC (5)
      • Monitoring (2)
    • Linux (1)
      • WSL (1)
    • Visual Studio (1)
    • Side Project (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 레벨2
  • 고득점 Kit
  • java
  • JPA
  • mariadb
  • DB
  • spring boot
  • Android Studio
  • 연습문제
  • 오블완
  • 티스토리챌린지
  • 고득점KIT
  • 레벨3
  • 알고리즘
  • kotlin
  • 안드로이드 스튜디오
  • nginx
  • docker
  • 프로그래머스
  • 백준

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
sggnology

하늘속에서IT

[Spring] Spring Security + MockMvc 로 테스트시 주의할 점(feat. kotlin)
Spring Boot

[Spring] Spring Security + MockMvc 로 테스트시 주의할 점(feat. kotlin)

2024. 6. 28. 10:49
728x90

발단

Spring Security 와 MockMvc 를  Kotlin 환경에서 검증을 하던 도중 설정(config)이 옳음에도 테스트의 결과가 일관된 반응을 하는 현상을 발견하여 그 이유를 알아보고 왜 그러하였는지 과정을 작성합니다.

 

 

 

Kotlin 고차함수 & 함수형 인터페이스

 

[Kotlin] 함수에 인자로 함수를 사용할 수 있다?(feat. 고차함수,

고차 함수 fun m1(action: () -> Unit) { action() }고차 함수는 함수를 인자로 받거나 함수를 반환하는 함수 입니다. Kotlin 에서는 함수 매개변수를 사용할 수 있습니다. 람다 표현식val sum: (Int, Int) -> Int

atsky.tistory.com

  • Kotlin 의 고차함수에 대하여 위 글을 통해 개념을 간단히 보시면 더 이해가 쉽습니다.
  • SAM(Single Abstract Method) 변환에 대해서도 다루고 있습니다.

 

MockMvc 호출을 Kotlin 으로 표현하는 2가지 방식

import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get

..

@Autowired
lateinit var mockMvc: MockMvc

// 정석적인 호출
mockMvc.perform(
	get("/api/test")
)..

// SAM 변환을 사용한 호출
mockMvc.perform {
	get("/api/test")
    	.buildRequest(it)
}..
  • 정석적인 호출: perform 함수의 매개변수인 `RequestBuilder` 를 반환 타입으로 가지는 함수(get, put..)를 호출하여 처리할 수 있습니다.
  • SAM 변환 호출: perform 의 반환타입인 `MockHttpServletRequest` 를 반환하게끔 처리할 수 있습니다.

 

2가지 방식을 호출할 가능성

  • 제가 사용하고 있는 Intellij 의 경우 SAM 변환이 가능한 호출의 경우 SAM 변환 방식을 선 추천해주고 있습니다.
  • 따라서, 2가지 방식 어느것이든 같은 결과를 가질 것이라 예측하고 의심없이 사용하게 될 수 있다고 생각됩니다.

 

Spring Security 와 사용시 문제 발생

..
@Test
@WithMockUser(roles = ["MANAGER"]) // spring security test 를 위한 annotation 으로 인증된 가상 사용자를 제공합니다.
fun test1() {
	..
    // `/api/test` 엔드포인트는 `MANAGER` 권한이 있어야 접근이 가능합니다.
    mockMvc
    	// 정석적인 호출
    	.perform(get("/api/test"))
        // SAM 변환 호출
        .perform {
        	get("/api/test").buildRequest(it)
        }
    	.andExpect {
        	// 인증된 가상 사용자 권한을 추가하였기에 검증을 통과하여야 합니다.
        	Assertions.assertEquals(it.response.status, 200)
        }
}
  • 정석적인 호출 사용시 @WithMockUser 로 제공한 가상 사용자 권한이 허용되어 검증을 통과합니다.
  • 그러나, SAM 변환 사용시 사용자 권한 검증에 통과하지 못하게 됩니다.

 

문제 발생 이유

  • 위에서 언급하였던 mockMvc.perform(..) 함수의 파라미터 타입은 `RequestBuilder` 입니다. 
  • 그런데 SAM 변환을 적용하면서 저는 RequestBuilder 타입을 반환하는 람다를 인자로 전달하였습니다.
  • 이는 ..perform(..) 내부 동작에 문제가 발생하게 만듭니다.

 

defaultRequestBuilder 와 Merge 가 되지 않아 TestSecurityContext 미적용

// MockMvc.perform 메서드 일부
public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
    // requestBuilder 는 Mergeable 인스턴스의 일부인지 묻고 있습니다.
    if (this.defaultRequestBuilder != null && requestBuilder instanceof Mergeable) {
        requestBuilder = (RequestBuilder)((Mergeable)requestBuilder).merge(this.defaultRequestBuilder);
    }
    ...
}

// (Mergeable)requestBuilder.merge 메서드 일부
public Object merge(@Nullable Object parent) {
    ...
    this.postProcessors.addAll(0, parentBuilder.postProcessors);
    return this;
    ...
}

// TestSecurityContextHolderPostProcessor.postProcessRequeset 메서드 일부
private static final class TestSecurityContextHolderPostProcessor extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor {
    ...
    public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
        ...
        // TestSecurityContext 를 SecurityContext 로 대체
        SecurityContext context = TestSecurityContextHolder.getContext();
        if (!this.EMPTY.equals(context)) {
            this.save(context, request);
        }

        return request;
        ...
    }
}
  • MockMvc.perform 코드블럭의 첫 if 문에 따라 requestBuilder 가 Mergeable 인스턴스에 포함된다면 merge 함수를 통해 defaultRequestBuilder 와 merge 해주고 있습니다.
  • 저는 requestBuilder 를 SAM 변환을 통해 람다로 제공하였기에 람다는 Mergeable 인스턴스가 아니라 merge 를 호출할 수 없었습니다.
  • merge 메서드 내부 동작을 살펴보면 parentBuilder(defaultRequestBuilder)로 부터 postProcessors 를 merge 하고 있는 것을 알 수 있습니다.

  • parentBuilder.postProcessors 내부에는 SecurityMockMvcRequestPostProcessors 클래스가 포함되어 있습니다.
  • 이 클래스의 postProcessRequest 메서드는 TestSecurityContext 를 현재 호출의 context 로 대체하여 줍니다.
  • 따라서, @WithMockUser 로 제공한 정보를 context 에 반영하게 되어 인증/권한 정보를 가진 채로 호출할 수 있게 됩니다.

 

결론

  • Builder 패턴혹은 호출전 다양한 설정이 적용되어야 하는 클래스의 경우 설정 적용을 위해 내부적으로 다양한 조건문을 사용할 수 있습니다.
  • 이는 인터페이스 혹은 고차함수 등으로 복잡한 인수가 포함될 수 있습니다.
  • 따라서, 정석적이 방식의 호출을 사용하는 것이 오류 확률을 줄여줄 것이라 생각합니다.
  • 위 경우처럼 본인이 구현한 동작이 아니고서야 SAM 변환은 줄이는 것이 좋겠다는 생각이 듭니다.
728x90

'Spring Boot' 카테고리의 다른 글

[Spring Boot] Assert 가 동작하지 않는다?(feat. Kotlin)  (0) 2024.11.22
[Spring Boot] Spring Security 설정 과정에서 `please use permitAll via HttpSecurity#authorizeHttpRequests instead` 문구 발생(feat. ignoring)  (1) 2024.05.28
[Spring] Custom Enum Value 를 Swagger 문서에 노출시키고 싶으면 어떻게 해야할까?  (0) 2024.05.21
[Spring Boot] Access denied for user 'root'@'localhost'(using password: yes)  (1) 2024.04.24
[Spring Boot] @ModelAttribute 로 맵핑하려는 대상의 depth 가 2 이상일 때 요청하였는데 오류가 난다면?(feat. kotlin)  (0) 2023.10.16
    'Spring Boot' 카테고리의 다른 글
    • [Spring Boot] Assert 가 동작하지 않는다?(feat. Kotlin)
    • [Spring Boot] Spring Security 설정 과정에서 `please use permitAll via HttpSecurity#authorizeHttpRequests instead` 문구 발생(feat. ignoring)
    • [Spring] Custom Enum Value 를 Swagger 문서에 노출시키고 싶으면 어떻게 해야할까?
    • [Spring Boot] Access denied for user 'root'@'localhost'(using password: yes)
    sggnology
    sggnology
    하늘은 파란색이니까 내 삶도 파란색이길 ㅎㅎ

    티스토리툴바