Spring 인증 사용자의 정보 저장

성종호·2024년 1월 6일
1
post-custom-banner

사용자 정보 저장

프로젝트를 진행하면서 사용자 정보를 저장하는 로직에 관해서 글을 써보려고 합니다. 일단 저는 Jwt기반의 토큰인증 방식으로 사용자 인증/인가를 진행하고 있는데요

멤버만 사용 가능한 API에서 멤버의 식별자를 어떤 방식으로 저장하고 왜 해당 방식을 선택하게 되었는지에 대해서 적어보겠습니다.

첫번째 방식 Request.setAttribute

이 방식을 첫번째로 생각한 이유는 제가 원래는 Express.js 로 개발을 하던때에 request 를 상속받고 user속성을 더해서 인증에서 사용하던 방식이였는데요
그러다보니 사용자 정보는 request에 담는것이 당연하다 라고 틀에 박힌 생각을 가지고 관련 자료를 찾아봤기 때문입니다.

Reqeust.setAttribute는 Request속성안에 내가 원하는 지정값을 세팅해서 저장하는 방식입니다.

request.setAttribute("userId", user.getId());

첫번째 인자에 키인 문자열값 두번째 인자에 값인 객체를 저장하는 방식입니다.

이렇게 저장한 사용자정보를 Controller에서 넘겨받을때는

@GetMapping("/main")
    public ResponseEntity<?> getMainCategory(@RequestAttribute("userId") Long userId){

        return BaseResponseEntity.ok(categoryService.getMainCategory(), "success");
    }

@RequestAttribute어노테이션을 이용하여서 AOP로 해당 인자에 값을 매핑시켜서 받아오는 방식으로 request에 저장된 값을 사용할수 있었습니다.

이 방식을 사용하면서 제가 느끼는 장단점으로는
장점
1. 사용법이 간단하다.
2. 이벤트루프 기반의 싱글스레드 방식에서도 사용하기 편하다.

단점
1. request에 의존적이므로 presentation보다 하위 계층에서 사용할때 인자를 넘겨주는 로직이 반복적으로 일어날수 있다.
2. 스레드 세이프하지 않다.
3. Aspect를 이용함으로 비용이 늘어난다.

제 생각에는 장점인 사용법이 간단하다는게 정말 좋았던것 같습니다. 그냥 객체에 속성을 추가해주고 해당 속성을 AOP로 가져오게 되면서 사용법이 간단하다는게 단점을 상쇄해주는것 같았습니다.


두번째 방식 ThreadLocal

ThreadLoacl은 각 스레드에게 독립적인 저장 공간을 제공하게 되는데요
대표적으로 DataSourceTransactionManager을 이용한 DB 커넥션이나 RequestContextHolder 처럼 현재 스레드에 할당된 request객체 등을 ThreadLocal에 저장되어 사용되게 됩니다.

ThreadLocal이 어떻게 구성되어있는지 먼저 확인하고 제가 어떤식으로 사용했는지 보도록 하겠습니다.

ThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread(); // 1
        ThreadLocalMap map = this.getMap(t); // 2
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // 3
            if (e != null) {
                T result = e.value; // 4
                return result;
            }
        }
        return this.setInitialValue(); // 5
    }
  1. 현재 사용되고있는 스레드를 가져온다.
  2. 스레드로컬맵에 현재 스레드를 키값으로 사용하고있는 객체를 가져온다.
  3. 가져온 객체에서 현재 스레드로컬 인스턴스를 키값으로 갖고있는 엔트리를 가져온다.
  4. 엔트리의 값을 result에 담아준다.
  5. 해당하는 값이 없으면 초기값을 설정하고 리턴한다. (초기값 = null)

그래서 저는 ThreadLocal을 이용하는 Manager class를 아래와 같이 정의하였습니다.

public class AuthenticatedUserThreadLocalManager {
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void set(Long userId) {
        threadLocal.set(userId);
    }

    public static Long get() {
        return threadLocal.get();
    }

    public static void remove() {
        threadLocal.remove();
    }
}

그리고 사용자 인증을 진행하는 Interceptor에서 아래와 같이 저장하였습니다.

AuthenticatedUserThreadLocalManager.set(user.getId());

이렇게 저장한 사용자정보를 Controller에서 넘겨받을때는

@GetMapping("/main")
    public ResponseEntity<?> getMainCategory(){

        return BaseResponseEntity.ok(
                categoryService.getMainCategory(AuthenticatedUserThreadLocalManager.get(),
                "success");
    }

class의 정적메소드를 사용하여 사용자정보가 필요한 인자값에 할당해주었습니다.

ThreadLocal을 사용하면서 제가 느끼는 장단점으로는
장점
1. 해당 스레드에서만 사용가능하기 때문에 스레드세이프하다.
2. 전역적으로 레이어 상관없이 사용하기에 간편하다.
3. 서블릿에 의존적이지 않다.

단점
1. 객체의 값을 지워주지 않으면 스레드로컬에 저장된 객체가 메모리를 계속 차지하고있으므로 메모리 누수가 일어날 수 있다.
2. 스레드풀을 사용할경우 의도치않게 이전에 저장된 객체를 다른 request스코프에서 재사용될수 있다.
3. request per one 즉 해당 스레드에서만 사용될수 있으며 멀티스레드를 이용할경우 의도치않은 방향으로 에러가 날 수 있다.

저는 그래서 결론적으로 ThreadLocal을 이용한 방식을 채택했는데요
그 이유로는 Request.setAttribute를 이용한 방식으로는 AOP를 사용해서 Aspect를 계속해서 분기처리 해주는 리소스가 어플리케이션이 커질수록 리소스가 많이 소비된다고 생각하기도 했고 현재는 request객체를 스레드당 하나씩 할당해주고 있기 때문에 ThreadLocal을 사용해도 무방하다고 생각해서 입니다.


마지막으로 더 좋은 방식이든 아니든 여러 방식을 알고싶은데요? 여러분들이 사용하는 방식의 사용자정보 저장을 댓글로 알려주시면 공부하겠습니다. 감사합니다.

profile
아자
post-custom-banner

0개의 댓글