티스토리 뷰

반응형

스프링에서 사용자의 요청은 필터를 거쳐 DispatcherServlet으로 넘어가고, 내부의 doDispatch() 메서드를 통해 해당 요청에 대한 

인터셉터들의 preHandle()이 처리된 후 컨트롤러로 넘어간다.

 

나의 경우 인터셉터에서 인증 관련 처리를 하기 위해 Authentication 헤더를 읽어 인증과 인가를 수행한다. 이후 ArgumentResolver 혹은 컨트롤러에서 다시 사용자의 이메일 혹은 아이디를 통한 처리를 진행한다.

 

이때 인터셉터에서 이미 암호화된 Authentication 헤더의 값을 해석하고 파싱 했음에도 값을 캐싱하지 않는다면 이후의 ArgumentResolver에서 다시 해석과 파싱 작업을 해야 한다.

 

이런 불편을 없애기 위해 컨트롤러 메서드 앞에서 요청이 처리될 때 값을 캐싱해 넘겨줄 수 있는 방법들을 알아보았다.

 


 

1. ThreadLocal

 

요청에 대한 처리가 하나의 쓰레드가 이루어진다면 ThreadLocal을 충분히 이용할 수 있다.

계정 저장을 위한 객체를 빈으로 등록한 뒤

아래와 같이 인터셉터의 preHandle()에서 ThreadLocal에 저장하고,

 

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

    // 로그인 처리 후 계정 정보가 담긴 authInfo 객체를 만든다
    AuthInfo authInfo = new AuthInfo(email, pw);
    
    . . .
    
    ThreadLocal<AuthInfo> local = new ThreadLocal<>();
    local.set(authInfo);
}

 

ArgumentResolver 혹은 Controller 등 필요한 위치에서 값을 꺼내올 수 있다.

주의할 점은 요청이 처리되고 난 뒤 쓰레드 풀에 쓰레드가 반납되므로, 해당 ThreadLocal에 저장한 값을 제거해야 한다.

사용 시점에 바로 제거하는 편이 간단하다.

 

public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
	
    @Autowired
    private ThreadLocal<AuthInfo> local;

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory
    ) {
    	local.get();
        local.clear();
        . . .

    }
}

 

 

ThreadLocal을 비워줘야 한다는 점이 약간 불편하며,

WebFlux를 사용하여 비동기식으로 요청이 처리되는 상황에서는 동일 쓰레드에서의 처리가 보장되지 않으므로 InheritableThreadLocal을 사용해야 한다.

 

 

2. HttpServletRequest에 값 저장하기

 

HttpServletRequest에 setAttribute()를 통해 값을 바로 저장할 수 있다. 

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    . . .

    request.setAttribute("email", email);
    request.setAttribute("password", password);
    return true;
}

 

HttpServletRequest 객체는 요청 별로 하나가 생성되며 attribute를 저장 시 ConcurrentHashMap에 key - value 형태로 저장된다.

 

사용 위치에서 request를 getAttribute(key_name)로 가져오기만 하면 된다.

ThreadLocal과 같이 사용 후 제거할 필요 없이 요청이 처리되면 스프링에 의해 삭제된다.

 

 

3. @RequestScope 사용하기

 

 Spring Core에서 빈이 기본적으로 싱글톤의 생명 주기를 갖게 하는 것과 달리, Spring MVC에선 HttpServletRequest와 마찬가지로 Request가 들어와 처리되는 동안으로 생명주기를 지정할 수도 있다.

 

@Component
@RequestScope
public class AuthInfo {
	. . .
}

 

위와 같이 요청이 처리될 동안으로 빈의 생명주기를 한정시키고, ArgumentResolver에서 AuthInfo를 주입받아 바로 사용할 수 있다.

마찬가지로 생명주기를 스프링이 알아서 관리하므로 편리하다. 빈으로 등록하고 필요한 위치에서 가져오기만 하면 된다.

 

 

 


 

추가로, 필터나 인터셉터에서 로깅 등을 위해 아래와 같이 요청을 읽어버리면 요청 객체의 바디 정보가 컨트롤러로 넘어오기 전에 사라진다.

이를 방지하기 위해서는 요청 혹은 응답을 캐싱할 수 있는 ContentCachingRequestWrapper를 사용해야 한다.

 

BufferedReader br = request.getReader();
logger.log(br.readLine());
// 요청 바디를 읽어버려 내용이 사라진 채로 넘어간다.

 

반응형