[Project] In-Memory-Database와 JWT 토큰 - 대용량 트래픽 (3)

이상혁·2024년 10월 29일

Project

목록 보기
7/12

들어가며

저번 포스트에서는 Scale Out의 세션 불일치 문제의 해결 방식으로 Sticky Session, Session-Clustering의 대해서 알아보았습니다. Sticky Session의 경우, 고정된 사용자와 서버를 사용하기 때문에 오히려 트래픽이 더 늘어나 Scale Out의 장점을 살리지 못 할 수 있었고 Session-Clustering의 경우에는 각 서버의 session의 데이터를 복사를 해야 하기 때문에 성능이 저하가 되는 단점이 있었습니다. 그래서 이번 포스트에서는 로그인을 할 때 세션 불일치 문제를 해결할 수 있는 다른 방법들을 알아보고 어떤 방법이 현재 프로젝트에 적합한 지를 알아보겠습니다.

Session Storage을 알아보자

세션 불일치 문제를 해결할 수 있는 방법 중 하나가 Session Storage를 이용을 하는 것이다.
Session Storage란 기존의 서버의 session을 이용하는 것이 아니라 외부에 따로 session 저장소를 만들어서 서버들이 이 session 저장소를 공유하도록 하는 방법을 말한다.

Session Storage를 이용을 하면 서버를 추가를 하더라도 추가한 서버에 Session Storage 주소만 추가를 하면 되기 때문에 기존 서버는 수정할 부분이 없다. 또한 같은 Session Storage를 공유를 하기 때문에 다른 곳에 복제를 해야 하는 과정을 거치지 않아서 성능적으로 좋다.

Session Storage의 종류

먼저, DiskDatabase와 In-Memory-Database가 있다.

DiskDatabase는 디스크에 데이터를 저장하는 것을 말합니다. 즉, 세션을 disk에 저장을 합니다. DiskDatabase의 장점으로는 비휘발성으로 전원 공급이 안 되더라도 데이터가 저장이 계속 되어 있는 장점이 있습니다. 하지만 디스크에 저장이 되기 때문에 데이터를 CPU까지 가지고 올 때 시간이 많이 걸린다는 단점이 있습니다.

In-Memory-Database의 경우 메모리에 세션을 저장을 하는 것을 말합니다. 그렇기 때문에 데이터를 가지고 오는 속도가 굉장히 빠릅니다. 하지만 휘발성이기 때문에 서버가 종료가 되거나 전원의 공급이 되지 않는다면 데이터가 날아가 버리는 단점이 있습니다.

이 두가지 종류 중에 어떤 종류가 Session Storage로 사용하기에 적합할까요?
In-Memory-Database를 사용하는 것 적합하다고 생각이 듭니다. 그 이유는 Session에 저장되는 데이터를 보면 영구적으로 저장이 필요없는 데이터입니다. 오히려 영구적으로 저장을 하면 안 되는 데이터들도 있습니다. 로그인을 예시로 들자면 session에 로그인 정보를 저장하고 판단을 하는데 일정 시간이나 기간이 지나면 데이터를 지우도록 합니다. 그렇기 때문에 In-Memory-Database를 사용하는 것이 적합하고 생각합니다.

In-Memory-Database는 단점이 뭐지?

그렇다면 로그인을 구현을 할 때 In-Memory-Database의 단점은 뭘까요?
In-Memory-Database는 결국 외부 저장소입니다. 그렇기 때문에 외부에서 데이터를 가지고 와야 하기 때문에 시간이 더 오래 걸릴 수 있습니다. Session의 경우는 서버 내부에 있기 때문에 Session 보다는 시간이 더 걸립니다. 또한 session의 경우 다른 서버의 session의 복제를 하지만 In-Memory-Database의 경우 하나이기 때문에 In-Memory-Database가 문제가 생기거나 다운이 되어 버린다면 모든 서버의 영향을 주는 경우가 발생을 합니다.

JWT 토큰 방식 알아보기

JWT 토큰 방식은 로그인을 할 때 사용자에게 JWT 토큰을 발급을 하고 이후에 요청을 할 때 발급한 토큰을 가지고 인증처리를 하는 방식을 말합니다. JWT 토큰은 클라이언트에서 요청을 보낼 때 같이 넣어서 보내면 토큰을 검증을 하고 인증을 해줍니다. JWT 토큰 방식의 장점은 무상태로 진행이 된다는 것입니다. 무상태란 요청을 처리를 할 때, 이전 요청의 상태나 다른 데이터를 참조하지 않고 독립작으로 처리를 한다는 장점이 있습니다. JWT 토큰을 이용하게 되면 로그인을 할 때 세션 불일치 문제를 걱정하거나 외부에 저장소를 만들 필요가 없어집니다. 저는 이러한 이유로 JWT 토큰을 이용하는 것이 프로젝트의 로그인을 구현하는 것에 있어서 적합하고 판단을 했습니다.

JWT 토큰으로 구현하기

LoginFilter로 로그인 했을 때, Token을 만들어서 넘겨주기

@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JWTUtil jwtUtil;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        String email = request.getParameter("email");
        String password = request.getParameter("password");

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
        return authenticationManager.authenticate(authenticationToken);

    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {

        ObjectMapper objectMapper = new ObjectMapper();
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();

        String email = customUserDetails.getEmail();
        Long id = customUserDetails.getId();
        String token = "Bearer " + jwtUtil.createJwt(email, id);

        LoginResponseDTO loginResponseDTO = new LoginResponseDTO(id, email, token);
        String result = objectMapper.writeValueAsString(loginResponseDTO);

        response.addHeader("Authorization",  token);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");

        response.getWriter().write(result);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
    }

}

먼저, LoginFilter을 만들어 줍니다. LoginFilter는 로그인을 했을 때 가입이 된 사용자가 맞는지 확인을 하고 토큰을 새로 만들어줍니다. 그리고 새로 만든 토큰을 응답 헤더에 Authorization 이라는 이름으로 담아서 클라이언트에게 보내줍니다.
successfulAuthenticationa메소드가 Token을 새로 만들어 주고 이를 요청 헤더에 담아서 보내주는 역할을 하는 메소드입니다.

Login 후 요청을 할 때, Token 검증하기

@RequiredArgsConstructor
public class JWTFilter extends OncePerRequestFilter {

    private final JWTUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
            throw new GlobalCommonException(AuthErrorResponsive.ABNORMAL_TOKEN);
        }

        String token = authorizationHeader.replace("Bearer ", "");

        if(!jwtUtil.isExpired(token)) {
            Authentication authentication = jwtUtil.getAuthentication(jwtUtil.getEmail(token));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }



        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return StringUtils.startsWithAny(request.getRequestURI(), "/login");
    }

}

doFilterInternal메소드를 보면 로그인 후 요청을 보낼 그 요청을 받아서 먼저, 토큰을 추출을 하고 검증을 하는 과정을 거칩니다. 그리고 검증이 완료가 되면 인증 정보를 secutiryContextHolder에 담아줍니다. 이후에 요청을 완료를 하고 응답을 넘겨줍니다.

결론

Scale Out으로 인한 세션 불일치를 문제를 해결하는 방식으로 외부에 Session Storage를 두는 방법에 대해서 알아보았습니다. Session Storage에는 DiskDatabase와 In-Memory-Database가 있고 DiskDatabase는 영구적으로 저장이 가능하지만 속도가 느리고 In-Memory-Database는 속도는 빠르지만 영구적으로 저장이 불가능합니다. 그래서 세션 정보는 영구적으로 저장을 할 필요가 없기 때문에 Session Storage를 사용을 한다면 In-Memory-Database가 적합하다고 판단을 하였습니다. 하지만 Session Storage의 경우 외부 서버이기 때문에 데이터를 가지고 오는 시간이 걸리고 또 Session Storage의 문제가 생기면 모든 서버에 영향을 줄 수가 있는 단점이 있었습니다. 그래서 저는 무상태로 독립적인 요청을 할 수 있는 JWT 토큰으로 로그인을 구현하는 것이 더 적합하다고 판단을 하였습니다. JWT은 로그인 시 JWT 토큰을 발급을 하고 그 토큰으로 인증 처리 후 응답을 줍니다. 그렇기 때문에 로그인에 한해서 session이 필요가 없고 외부 Session Storage를 둘 필요가 없습니다. 그래서 JWT 토큰 방식이 더 적합하다고 판단을 했습니다.

profile
꾸준히!

0개의 댓글