λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

ν”„λ‘œμ νŠΈ/Airbnb Clone

[νŠΈλŸ¬λΈ” μŠˆνŒ…] ν…ŒμŠ€νŠΈλ‘œ μ•Œμ•„λ³΄λŠ” μ»¨νŠΈλ‘€λŸ¬μ—μ„œ HttpSession μ‚¬μš© μ‹œ 주의점

Junit5 및 Mock 에 λŒ€ν•΄ μ„€λͺ…λ“œλ¦¬μ§€ μ•ŠλŠ” 점 μ–‘ν•΄ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.
ν…ŒμŠ€νŠΈ μ½”λ“œλ³΄λ‹€λŠ” μ»¨νŠΈλ‘€λŸ¬μ—μ„œ HttpSession 을 μ‚¬μš©ν•  λ•Œμ˜ μ£Όμ˜μ μ„ μ•Œμ•„λ³΄λŠ” μ‹œκ°„μ„ κ°€μ Έκ°€μ…¨μœΌλ©΄ μ’‹κ² μŠ΅λ‹ˆλ‹€.

 

μ»¨νŠΈλ‘€λŸ¬μ—μ„œ νŒŒλΌλ―Έν„°λ‘œ HttpSession 을 μ£Όμž…λ°›μ•„μ„œ μ‚¬μš©ν•  λ•Œ μ£Όμ˜ν•  점이 μžˆμŠ΅λ‹ˆλ‹€.

 

μ™œ μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ”μ§€ HttpSession κ³Ό HttpServletRequest.getSession() 의 차이λ₯Ό ν…ŒμŠ€νŠΈ μ½”λ“œλ‘œ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

⭐ HttpSession λ₯Ό λ©”μ„œλ“œμ˜ νŒŒλΌλ―Έν„°λ‘œ μ¦‰μ‹œ μ£Όμž… λ°›κΈ°

  • μ½”λ“œ μ„€λͺ…

- νšŒμ›μ΄ 인증 μ½”λ“œλ₯Ό 잘λͺ» μž…λ ₯ν•΄μ„œ 이메일 검증에 μ‹€νŒ¨ν•˜λ©΄ if 문이 거짓이 λ©λ‹ˆλ‹€.

- Session 에 값을 μ„€μ •ν•˜μ§€ μ•Šκ³ , λ©”μ„œλ“œκ°€ μ’…λ£Œλ©λ‹ˆλ‹€.

@GetMapping("/email/authenticate")
public ApiResponse<Void> authenticateEmail(@RequestBody @Valid EmailAuthenticationRequest request,
                                           HttpSession session) {
    if (mailService.isValidMail(request.toServiceRequest())) {
        session.setAttribute(MAIL_VERIFIED_MEMBER, true);
        return ApiResponse.ok();
    }
    return ApiResponse.status(HttpStatus.BAD_REQUEST);
}

 

  • ν…ŒμŠ€νŠΈ μ½”λ“œ

이 λ©”μ„œλ“œκ°€ if문이 거짓이 될 λ•Œ, μ„Έμ…˜μ΄ 생성이 λ˜μ§€ μ•ŠλŠ”μ§€ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ§œλ³΄κ² μŠ΅λ‹ˆλ‹€.

@DisplayName("이메일 인증이 μ‹€νŒ¨ν•˜λ©΄ HttpSession은 μƒμ„±λ˜μ§€ μ•ŠλŠ”λ‹€.")
@Test
void ν…ŒμŠ€νŠΈ() throws Exception {
    // given
    String email = "123@naver.com";
    String authenticationCode = "312njacz";
    
    EmailAuthenticationRequest request = new EmailAuthenticationRequest(email, authenticationCode);

    //==Mock 객체λ₯Ό μ‚¬μš©ν•΄μ„œ 이메일 검증에 μ‹€νŒ¨ν–ˆμ„ λ•Œλ₯Ό κ°€μ •==
    given(mailService.isValidMail(request.toServiceRequest()))
           .willReturn(false);

    // when then
    MvcResult mvcResult = mockMvc.perform(
                    get("/member/email/authenticate")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
            .andDo(print())  //==MockHttpServletRequest/Response λ‚΄μš©μ„ μ½˜μ†”μ— 좜λ ₯==
            .andReturn();

   //==getSession(false)λ₯Ό ν•΄μ•Ό μ„Έμ…˜μ΄ 없을 λ•Œ, MockHttpSession 이 λ§Œλ“€μ–΄ 지지 μ•ŠμŒ==
   HttpSession session = mvcResult.getRequest().getSession(false);
   Object sessionValue = mvcResult.getRequest().getSession(false).getAttribute(MAIL_VERIFIED_MEMBER);

   assertThat(session).isNull();
   assertThat(sessionValue).isNull();
}

 

 

μ›λž˜ μ˜λ„λŒ€λ‘œλΌλ©΄ νšŒμ›μ€ 이메일 검증에 μ‹€νŒ¨ν–ˆκΈ° λ•Œλ¬Έμ— μ„œλ²„λŠ” μ„Έμ…˜μ„ μƒμ„±ν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€.

그렇기에 assertThat(session).isNull() κ³Ό assertThat(sessionValue).isNull() 의 κ²°κ³ΌλŠ” 성곡해야 ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ, ν…ŒμŠ€νŠΈ κ²°κ³ΌλŠ” μ•„λž˜ μ‚¬μ§„μ²˜λŸΌ μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.

  • session κ³Ό sessionValue 에 값이 μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

  • POSTMAN 으둜 확인을 해도 μ„Έμ…˜μ΄ 생성됨을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

  • μΈν…”λ¦¬μ œμ΄μ˜ http 으둜 확인을 해도 μ„Έμ…˜μ΄ 생성됨을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

β­μ™œ HttpSession 이 μƒμ„±λ˜μ–΄ μΏ ν‚€λ₯Ό λ°˜ν™˜ν• κΉŒμš”?

HttpSession 의 κ³΅μ‹λ¬Έμ„œμ€‘ μΌλΆ€μž…λ‹ˆλ‹€.

ν•΄λ‹Ή λ¬Έμ„œμ—μ„œλŠ” HttpSession 은 ServletContext 의 μŠ€μ½”ν”„μ— μ˜ν•΄ κ΄€λ¦¬λ˜μ§€, μŠ€ν”„λ§Context μ˜μ—­μ΄ μ•„λ‹™λ‹ˆλ‹€.

단지 μŠ€ν”„λ§μ€ HttpSession 을 ν†°μΊ£μ˜ ServletContext λ‘œλΆ€ν„° κ°€μ Έμ˜¬ λΏμž…λ‹ˆλ‹€.

Session information is scoped only to the current web application (ServletContext), so information stored in one context will not be directly visible in another.

 

κ·Έλž˜μ„œ HttpSession 을 컨트둀러의 νŒŒλΌλ―Έν„°μ— μ‚¬μš©ν•˜κ²Œ λœλ‹€λ©΄ ν•΄λ‹Ή λ©”μ„œλ“œκ°€ 호좜될 λ•Œλ§ˆλ‹€ μŠ€ν”„λ§μ€ HttpSession 을 ν†°μΊ£μœΌλ‘œλΆ€ν„° 가져와 μ£Όμž…ν•˜κ²Œ λ©λ‹ˆλ‹€.

 

⭐ HttpServletRequest.getSession() λ₯Ό μ‚¬μš©ν•˜κΈ°

HttpSession 의 side-effect 에 μ•Œμ•„λ΄€μœΌλ‹ˆ HttpServletRequest.getSession() λ₯Ό μ‚¬μš©ν•˜λ©΄ μ΄λ©”일 검증이 μ‹€νŒ¨ν–ˆμ„ λ•Œ(if문이 거짓일 λ•Œ) μ„Έμ…˜μ΄ λ§Œλ“€μ–΄ 지지 μ•ŠλŠ”μ§€ 확인해 λ³΄κ² μŠ΅λ‹ˆλ‹€.

  • μ½”λ“œ μ„€λͺ…

- λ§ˆμ°¬κ°€μ§€λ‘œ νšŒμ›μ΄ 인증 μ½”λ“œλ₯Ό 잘λͺ» μž…λ ₯ν•΄μ„œ 이메일 검증에 μ‹€νŒ¨ν•˜λ©΄ if 문이 거짓이 λ©λ‹ˆλ‹€.

- Session 에 값을 μ„€μ •ν•˜μ§€ μ•Šκ³ , λ©”μ„œλ“œκ°€ μ’…λ£Œλ˜μ•Ό ν•©λ‹ˆλ‹€.

@GetMapping("/member/email/authenticate")
public ApiResponse<Void> authenticateEmail(@RequestBody @Valid EmailAuthenticationRequest request,
                           바뀐 λΆ€λΆ„  ====> HttpServletRequest servletRequest) { <====
    if (mailService.isValidMail(request.toServiceRequest())) { 
        HttpSession session = servletRequest.getSession();
        session.setAttribute(MAIL_VERIFIED_MEMBER, true);
        return ApiResponse.ok();
    }
    return ApiResponse.status(HttpStatus.BAD_REQUEST);
}

 

  • ν…ŒμŠ€νŠΈ μ½”λ“œ

ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ ν†΅κ³Όν•˜λ©° μ„Έμ…˜μ΄ λ§Œλ“€μ–΄ μ§€μ§€μ•ŠλŠ” 것을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

 

⭐결둠

1. 컨트둀러 λ©”μ„œλ“œ νŒŒλΌλ―Έν„°μ— HttpSession 을 λ°”μΈλ”©ν•œλ‹€λ©΄ μŠ€ν”„λ§μ€ ServletContext μ—μ„œ μ„Έμ…˜μ„ κ°€μ Έμ˜€λ©°, ServletContext μž…μž₯μ—μ„œλŠ” μŠ€ν”„λ§μ—μ„œ μ„Έμ…˜μ„ μ‚¬μš©ν•œλ‹€κ³  κ°€μ •ν•˜κ³ , "JSESSIONID" λΌλŠ” μ΄λ¦„μ˜ μΏ ν‚€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

 

2. κ·ΈλŸ¬λ‹ˆ, 쑰건에 따라 HttpSession 을 생성해야 ν•œλ‹€λ©΄ HttpSession λŒ€μ‹  HttpServletRequest λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

 

3. HttpServletRequest 을 μ‚¬μš©ν•  λ•Œ, μŠ€ν”„λ§μ΄ ServletContext μ—μ„œ HttpSession 을 κ°€μ Έμ˜€λŠ” μ‹œμ μ€ .getSession() 을 ν˜ΈμΆœν•  λ•Œμž…λ‹ˆλ‹€.

 

4. μΆ”κ°€λ‘œ μ„Έμ…˜μ„ μ‚¬μš©ν•  λ•ŒλŠ” .getSession() vs .getSession(false) 의 차이도 μ•Œμ•„μ•Ό ν•©λ‹ˆλ‹€.

.getSession() 은 μ„Έμ…˜μ΄ 있으면 μ„Έμ…˜μ„ λ°˜ν™˜ν•˜κ³  μ„Έμ…˜μ΄ μ—†μœΌλ©΄ μƒˆλ‘œμš΄ μ„Έμ…˜μ„ μƒμ„±ν•©λ‹ˆλ‹€.

.getSession(false) 은 μ„Έμ…˜μ΄ 있으면 μ„Έμ…˜μ„ λ°˜ν™˜ν•˜κ³  μ„Έμ…˜μ΄ μ—†μœΌλ©΄ null 을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

* Difference Between request.getSession() and request.getSession(true)