λ³Έ 글은 λ ˆλ””μŠ€ κ΄€λ ¨ 결함을 κ²½ν—˜ν•œ 것을 ν† λŒ€λ‘œ μŠ€ν”„λ§ μ„Έμ…˜ λ ˆλ””μŠ€μ— λŒ€ν•œ λ™μž‘μ„ μ •λ¦¬ν•˜κΈ° μœ„ν•΄μ„œ μž‘μ„±ν•œ 것 μž…λ‹ˆλ‹€.

λ§Žμ€ μžλ°” κ°œλ°œμžκ°€ μŠ€ν”„λ§ λΆ€νŠΈ ν”„λ‘œμ νŠΈλ₯Ό μ‚¬μš©ν•˜λŠ” μ΄μœ λŠ” 별닀λ₯Έ μ½”λ“œ κ΅¬ν˜„ 없이도 μ—¬λŸ¬ κ°œλ°œμžλ“€μ— μ˜ν•΄ μž‘μ„±λ˜μ–΄μ§„ λ‘œμ§μ„ μžλ™μœΌλ‘œ κ΅¬μ„±ν•˜λ©΄μ„œ 쉽고 λΉ λ₯΄κ²Œ μ›ν•˜λŠ” κΈ°λŠ₯κ³Ό λ™μž‘μ„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μ μš©μ‹œν‚€κΈ° μœ„ν•œ λͺ©μ μ΄ 크닀고 μƒκ°ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ, μžλ°” 뿐만 μ•„λ‹ˆλΌ λ‹€λ₯Έ μ–Έμ–΄μ˜ ν”„λ ˆμž„μ›Œν¬μ—μ„œλ„ κ΅¬ν˜„λœ μ½”λ“œλ“€μ„ μ „λΆ€ ν™•μΈν•˜κ³  μ‚¬μš©ν•˜μ§€λŠ” μ•Šμ„ κ²½μš°κ°€ λ§Žμ„ν…λ°μš”. μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ˜ κ΅¬ν˜„ λ²”μœ„κ°€ μƒλ‹Ήνžˆ λ§Žλ‹€λ³΄λ‹ˆ μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ˜ κ°œλ… ν˜Ήμ€ λ™μž‘μ— λŒ€ν•΄μ„œ λŒ€μΆ© μ΄ν•΄ν•˜κ³  λ„˜μ–΄κ°€κ±°λ‚˜ μ•„λŠ” μ„ μ—μ„œ μ‚¬μš©ν•˜λŠ” νŽΈμž…λ‹ˆλ‹€.

μŠ€ν”„λ§ μ„Έμ…˜ λ ˆλ””μŠ€μ— μ˜ν•œ 결함을 λ§Œλ“€μ–΄λ‚Έ μ΄μœ λ„ RequestContextHolderλ₯Ό ν†΅ν•΄μ„œ μŠ€λ ˆλ“œ λ‘œμ»¬μ— μ €μž₯된 μš”μ²­ 정보λ₯Ό κ°€μ Έμ˜¬ 수 있고 RequestContextHolder둜 λΆ€ν„° κ°€μ Έμ˜¨ RequestAttributes에 μ„Έμ…˜ 아이디λ₯Ό κ°€μ Έμ˜¬ 수 μžˆλŠ” ν•¨μˆ˜κ°€ μžˆκΈ°μ— μ‚¬μš©ν–ˆλ˜ κ²ƒμœΌλ‘œλΆ€ν„° μ‹œμž‘λ©λ‹ˆλ‹€. 개인적인 κ²½ν—˜μœΌλ‘œ λ³Όλ•ŒλŠ” μŠ€ν”„λ§ μ„Έμ…˜κ³Ό λ ˆλ””μŠ€λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” 것은 λ‹¨μˆœν•˜κ²Œ μŠ€ν”„λ§ μ„Έμ…˜ λ ˆλ””μŠ€μ— λŒ€ν•œ μ˜μ‘΄μ„±λ§Œ μΆ”κ°€ν•˜κ³  λ ˆλ””μŠ€μ— μ—°κ²°ν•  수 μžˆλŠ” 정보 그리고 μŠ€ν”„λ§ μ„Έμ…˜μ„ ν™œμ„±ν™”ν•˜λŠ” μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜λŠ” 것 λΏμ΄λ―€λ‘œ λ‚΄μž¬λœ μ½”λ“œκ°€ μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€ μ œλŒ€λ‘œ 확인할 ν•„μš”μ„±μ€ μ—†μ—ˆμŠ΅λ‹ˆλ‹€.

TCP와 HTTP의 μ„Έμ…˜μ€ λ‹€λ₯΄λ‹€.

Spring Session provides transparent integration with HttpSession. This means that developers can switch the HttpSession implementation out with an implementation that is backed by Spring Session.

TCPμ—μ„œμ˜ μ„Έμ…˜μ€ 연결을 μ˜λ―Έν•˜μ§€λ§Œ HTTPμ—μ„œμ˜ μ„Έμ…˜μ€ 연결에 λŒ€ν•œ μƒνƒœλ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€. μŠ€ν”„λ§ μ„Έμ…˜μ€ TCP 레벨이 μ•„λ‹Œ HTTP μ„Έμ…˜μ— λŒ€ν•œ 톡합을 μ§€μ›ν•©λ‹ˆλ‹€. 그리고 κΈ°λ³Έμ μœΌλ‘œλŠ” λ©”λͺ¨λ¦¬μ— μ„Έμ…˜μ„ μ €μž₯ν•˜κ²Œ λ˜λŠ” 것을 JDBC 기반으둜 κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•œλ‹€κ±°λ‚˜ λ ˆλ””μŠ€λ₯Ό μ‚¬μš©ν•΄μ„œ μ„Έμ…˜ μ €μž₯μ†Œλ‘œ ν™œμš©ν•  수 μžˆλ„λ‘ μ œκ³΅ν•˜λŠ” 것도 ν¬ν•¨ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

WASλŠ” μ„Έμ…˜ 관리λ₯Ό μ§€μ›ν•œλ‹€.

ν†°μΊ£μ΄λ‚˜ μ–Έλ”ν† μš°μ™€ 같은 WASμ—μ„œλ„ 자체적으둜 λ©”λͺ¨λ¦¬ 기반의 μ„Έμ…˜μ„ μ§€μ›ν•˜λ„λ‘ κ΅¬ν˜„λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€. μŠ€ν”„λ§ μ„Έμ…˜μ—μ„œλŠ” μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆ(WAS)κ°€ 자체적인 μ„Έμ…˜μ„ μƒμ„±ν•˜μ§€ μ•Šλ„λ‘ AbstractHttpSessionApplicationInitializerλ₯Ό 톡해 springSessionRepositoryFilter μ΄λΌλŠ” μ΄λ¦„μ˜ νŠΉμˆ˜ν•œ ν•„ν„°λ₯Ό λ“±λ‘ν•˜μ—¬ λͺ¨λ“  μš”μ²­μ— λŒ€ν•΄μ„œ μ²˜λ¦¬λ˜λ„λ‘ μš”κ΅¬ν•©λ‹ˆλ‹€.

Fortunately, both HttpSession and HttpServletRequest (the API for obtaining an HttpSession) are both interfaces. This means that we can provide our own implementations for each of these APIs.
This highlights why it is important that Spring Session’s SessionRepositoryFilter be placed before anything that interacts with the HttpSession.

μŠ€ν”„λ§ μ„Έμ…˜μ€ 자체 κ΅¬ν˜„ μ„Έμ…˜μœΌλ‘œ μ „ν™˜ν•œλ‹€.

SessionRepositoryFilterκ°€ μˆ˜ν–‰ν•˜λŠ” μ€‘μš”ν•œ 역할은 μžλ°” μ„œλΈ”λ¦Ώ μŠ€νŽ™μ˜ HTTP μ„Έμ…˜μ„ 자체적인 μ„Έμ…˜ 클래슀둜 μ „ν™˜ν•˜λŠ” 것 μž…λ‹ˆλ‹€. 그리고 λ‚΄λΆ€μ μœΌλ‘œ HTTP μ„Έμ…˜κ³Ό μŠ€ν”„λ§ μ„Έμ…˜μ„ μ—°κ²°ν•˜κΈ° μœ„ν•΄μ„œ μΏ ν‚€ 기반의 HttpSessionIdResolverλ₯Ό μ‚¬μš©ν•˜λ„λ‘ λ˜μ–΄μžˆμ£ . κ²°κ΅­ SessionRepository κ΅¬ν˜„μ²΄μ— 따라 JDBC 기반으둜 λ°μ΄ν„°λ² μ΄μŠ€ μ„Έμ…˜ 정보λ₯Ό μ €μž₯ν•˜λŠ”μ§€ λ ˆλ””μŠ€μ— μ €μž₯ν•˜λŠ”μ§€ κ΅¬λΆ„λ˜μ–΄μ§€λŠ” κ²ƒμž…λ‹ˆλ‹€.

Switches the HttpSession implementation to be backed by a Session. The SessionRepositoryFilter wraps the HttpServletRequest and overrides the methods to get an HttpSession to be backed by a Session returned by the SessionRepository.

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
            response);

    try {
        filterChain.doFilter(wrappedRequest, wrappedResponse);
    }
    finally {
        wrappedRequest.commitSession();
    }
}

SessionRepositoryFilter의 μœ„ κ΅¬ν˜„μ²˜λŸΌ κ°€μž₯ λ¨Όμ € 처리됨으둜써 μš”μ²­ μŠ€λ ˆλ“œ λ‚΄λΆ€μ—μ„œ λ³€κ²½λ˜μ–΄μ§„ μ„Έμ…˜μ— λŒ€ν•΄μ„œλŠ” 응닡이 μ™„λ£Œλœ 이후에 μ΅œμ’…μ μœΌλ‘œ λ°˜μ˜λœλ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ κ²½ν—˜ν–ˆλ˜ λ ˆλ””μŠ€ κ²°ν•¨μ—μ„œλ„ μ„œλΉ„μŠ€ ν˜Ήμ€ νΌμ‹œμŠ€ν„΄μŠ€ λ ˆμ΄μ–΄μ—μ„œ μ„Έμ…˜μ΄ μƒμ„±λ˜λ”λΌλ„ μ„Έμ…˜ 정보λ₯Ό λ ˆλ””μŠ€μ— μ €μž₯ν•˜κ²Œ 된 κ²ƒμž…λ‹ˆλ‹€.

λ ˆλ””μŠ€μ— μ €μž₯λ˜λŠ” μžμ„Έν•œ λ‚΄μš©μ€ μŠ€ν”„λ§ μ„Έμ…˜ 곡식 λ¬Έμ„œμ˜ Storage Detailsμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

μŠ€ν”„λ§ μ„Έμ…˜ λ ˆλ””μŠ€λŠ” μŠ€μΌ€μ€„λ§μ„ 톡해 만료된 ν‚€λ₯Ό μ‚­μ œν•œλ‹€.

SessionCleanupConfigurationμ—μ„œ RedisSessionExpirationPolicy의 cleanExpiredSessions ν•¨μˆ˜λ₯Ό μŠ€μΌ€μ€„λŸ¬μ— λ“±λ‘ν•˜μ—¬ μŠ€ν”„λ§μ—μ„œ 자체적으둜 μ œκ³΅ν•˜λŠ” μŠ€μΌ€μ€„λ§μ— μ˜ν•΄ 만료된 ν‚€κ°€ μ‚­μ œλ©λ‹ˆλ‹€. λ‹€λ§Œ, λ ˆλ””μŠ€μ˜ 만료 이벀트의 타이밍 문제둜 μΈν•΄μ„œ TTL이 만료된 이후에 λ ˆλ””μŠ€κ°€ μ•Œμ•„μ„œ μ‚­μ œν•˜λ„λ‘ μŠ€ν”„λ§ μ„Έμ…˜ λ ˆλ””μŠ€μ—μ„œλŠ” λͺ…μ‹œμ μœΌλ‘œ ν‚€λ₯Ό μ‚­μ œν•˜μ§€ μ•Šκ³  λ‹¨μˆœνžˆ 쑰회(μ•‘μ„ΈμŠ€)ν•©λ‹ˆλ‹€.

We do not explicitly delete the keys, since, in some instances, there may be a race condition that incorrectly identifies a key as expired when it is not. Short of using distributed locks (which would kill our performance), there is no way to ensure the consistency of the expiration mapping. By simply accessing the key, we ensure that the key is only removed if the TTL on that key is expired.