최원종의 개발 블로그

V6-2 expose-session-attributes 의 false 사용 본문

Spring boot 입문

V6-2 expose-session-attributes 의 false 사용

chl6698 2026. 5. 20. 15:41

OSIV false 충돌

OSIV를 false로 설정했다는 것은 뷰(Mustache) 템플릿이 렌더링될 때는
이미 DB와의 연결(영속성 컨텍스트)이 완전히 끊어진 상태 (정석적인 설계 방식)

 

현재 코드 상태

spring:
  mustache:
    servlet:
      # 머스태치 템플릿 엔진에서 request 객체와 세션 객체에 접근할 수 있도록 허용하는 설정 추가
      expose-session-attributes: true
      expose-request-attributes: true
그런데 expose-session-attributes: true를 열어두면,
개발자가 무심코 세션에 통째로 저장해 둔 엔티티(Entity) 객체를
머스태치 템플릿에서 직접 꺼내 쓸 수 있게 됨.
만약 세션에서 꺼낸 엔티티 안에 지연 로딩(LAZY)으로 설정된 데이터가 있고 템플릿에서 그걸 출력하려고 한다면
DB 연결이 이미 끊겼기 때문에 여지없이  LazyInitializationException이 터지게 됩니다

코드 수정

spring:
  mustache:
    servlet:
      # 머스태치 템플릿 엔진에서 request 객체와 세션 객체에 접근할 수 있도록 허용하는 설정 추가
      expose-session-attributes: false
      expose-request-attributes: false

 

SessionInterceptor 코드 (새로 생성)

파일 위치

package com.tenco.blog._core.interceptor;

import com.tenco.blog.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component // IoC 처리
public class SessionInterceptor implements HandlerInterceptor {

    // 컨트롤러 로직이 거의 끝나는 시점 즉 화면이 그려지기 직전에 SessionUser 값을 주입 할 예정
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        ///HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);

        // 1. 화면을(View) 반환하는 요청인지 먼저 확인한다. /** <-- 모든 URL 요청시 동작
        // 데이터만(JSON) 반환 하는 요청일 경우 modelAndView값 없으므로 건너뜁니다.
        if(modelAndView != null) {
            // 화면을 반환하는 동작이다.
            // request.getSession(true);
            // request.getSession(false);
            // request.getSession() <--- 기본값은 TRUE 입니다.
            HttpSession session = request.getSession(false);
            // request.getSession(false) 로 설정하는 이유 !!
            // 만약 A 라는 사용자가 우리 서버에 최초 요청일 경우 스프링이 자동으로 세션을 만들어라
            // 동작하게 됩니다. 성능때문에 매번 세션메모리를 생성하지 마
            if(session != null) {
                User sessionUser = (User) session.getAttribute("sessionUser");
                if(sessionUser != null) {
                    modelAndView.addObject("sessionUser", sessionUser);
                }
            }
        }
    }
}

 

WebMvcConfig 코드 (수정)

package com.tenco.blog._core.config;

import com.tenco.blog._core.interceptor.LoginInterceptor;
import com.tenco.blog._core.interceptor.SessionInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// 자바 코드로 스프링 부트 설정 파일을 다둘 수 있다.

// @Component
@Configuration // IoC 대상 - 하나 이상의 IoC 처리를 하고 싶을 때 사용 한다.
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired // DI 처리
    private LoginInterceptor loginInterceptor;
    @Autowired // DI 처리
    private SessionInterceptor sessionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 화면에 SessionUser 정보를 내려줄 사용 됨. 
        registry.addInterceptor(sessionInterceptor)
                .addPathPatterns("/**"); // 모든 URL 요청서 동작 함


        // 인증 처리 인터셉터 동작 함
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/board/**", "/user/**")
                .excludePathPatterns(
                    // 로그인 관련(인증이 필요 없는 페이지)
                    "/login-form",  // 로그인 화면 요청 시
                    "/join-form",   // 회원 가입 화면 요청 시
                    "/logout", // 로그아웃

                    // 게시글 조회 관련 (인증 없이도 볼 수 있는 페이지)
                    "/board/list",  // 게시글 목록 화면 요청 시
                    "/"          ,  // 메인 페이지
                    "/index"          ,  // 메인 페이지
                    "/board/{id:\\d+}", // 게시글 상세보기( 숫자 ID만 허용)

                    // 정적 리소스 (CSS, JS, 이미지 등)
                    "/css/**",          // CSS 파일 제외
                    "/js/**",           // JS 파일 제외
                    "/images/**",      //  이미지 파일 제외
                    "/favicon.ico",    // 파비콘 제외

                    // H2 데이터베시스 콘솔( 개발 환경용)
                    "/h2-console/**"    // H2 콘솔 접근
                );
    }
}