Spring Boot - 동시 접속 유저수 제한(feat. JWT 토큰과 세션 사용에 대한 작은 고찰) (1)

Yunny.Log ·2022년 8월 22일
0

Spring Boot

목록 보기
76/80
post-thumbnail

클라이언트의 요구사항

현재 프로젝트에 접속한 사람이 50명 이하가 되도록 하는 것

설계 야망

50번 초과 사람이 (51번째 사람) 로그인을 하면 , alert 창으로 추가 구매가 필요합니다 라는 문구 띄우도록 할 것 😎

세션에 대한 이해

  • 스프링 시큐리티에서 제공하는 기본 설정으로 회원을 가입, 로그인, 로그아웃 시킬 때, 즉 한 유저가 로그인 될 때마다 생성되고 로그아웃 하면 사라지는 것이 세션이다.
  • 세션수가 결국 현재 접속하고 있는 유저 수라고 생각하면 된다.

1차 시도

  • 처음에 가벼운 마음으로 구글링하다가 위의 세션에 대한 이해를 했다고 착각한 후 아~ 그렇구나 라고 무지성으로 받아들였다.
  • 세션 수를 제한시켜주는 법에 대해서 구글링하다가, https://www.youtube.com/watch?v=k6IB8Dc3s6Y 를 보고, 한 프로그램에서 세션 수를 제한해주는
    SecurityConfig.java 에 아래 내용을 추가했다.
                .sessionManagement()
                .maximumSessions(1) //세션 수 제한
                .maxSessionsPreventsLogin(true);
                // 세션 수 제한 디폴트는 기존 유저 로그아웃
                // 위 옵션 설정 시 새로운 유저 로그인 제한 시키는 것으로 세션 갯수 유지

  • 그러나 나는 너무나도 여러 유저들이 잘 로그인 되는 것이었다. 🤣
  • 나는 스프링 시큐리티에서 제공해주는 기본 유저로 사용하는 것이 아닌 jwt 토큰을 사용하기 위해 내가 커스터마이즈한 유저를 사용했기 때문이었다. (벌써 5개월 전에 다루었던 것이라 잊고있었다..!)
  • 당연히 스프링 시큐리티 기본 설정 사용을 가정하고 코드를 짜신 백기선님의 설정과 확연히 다를 수 밖에!

2차 시도 전의 갈등 & 생각

  • jwt 의 최대 장점은 기존 로그인 방식과 달리 DB에서 따로 세션을 관리하지 않고, 검사하지 않아도 되어서 더욱 빠르고 DB에 부담이 가지 않는 기능을 제공해준다는 점이다.
  • 그러나 현재 동시 접속 유저수를 관리하기 위해서는, 유저가 저장될 때마다 세션을 내가 생성해서 저장해줘야 하는 상황
  • So, 처음에 나는 DB에 저장하지 않기로 결심하고, 그렇다면 괜찮을 것이라는 생각이 들었다.
    • 그러나 내가 프로그램을 Run 이나 BUILD를 다시 하면 싹 다 초기화가 되어버려서 동시접속자 판단에 오류가 생길 것이 뻔한 노릇!

Therefore, 나는 레디스라는 작고 빠르고 경량화된 비관계형 DB를 선택해서, 여기에는 딱 세션만 저장하게 하도록 결심했다.

레디스란 ? NoSQL로서 Key-Value 타입의 저장소인 레디스(Redis, Remote Dictionary Server)

  • 최대한 DB 에 부담을 덜고 싶었고, 정말 캐시 정도의 기능만 나는 필요로 했기 때문에, 레디스가 가장 적합하다고 생각했다.
  • 기존에 쓰고 있던 관계형 DB까지 쓸 필요가 전혀 없기 때문이지.

2차 시도

  • 우선 기존 SESSION 인증 방식을 사용하지 않을 것이기 때문에 설정에서 아래 형광펜 친 아이를 설정해준 상태


  • 그리고 세션을 CREATE, EXPIRE 해주는 파일을 추가로 작성했다.

SessionManager.java


package eci.server.config.security;

import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class SessionManager {

    private static final String SESSION_COOKIE_NAME = "simultaneous-connection-chk-session";

    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    /**
     * 세션 생성
     */
    public void createSession(Object value, HttpServletResponse response){
        // 세션 id를 생성하고, 값을 세션에 저장
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);

        // 쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);

        for(String map : sessionStore.keySet()){
            System.out.println("key " + map);
            System.out.println("value " + sessionStore.get(map));
        }
    }

    /**
     * 세션 조회
     */
    public Object getSession(HttpServletRequest request){

        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie == null){
            return null;
        }

        return sessionStore.get(sessionCookie.getValue());
    }

    /**
     * 세션 만료
     */
    public void expire(HttpServletRequest request){
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie != null){
            sessionStore.remove(sessionCookie.getValue());
        }
    }

    private Cookie findCookie(HttpServletRequest request, String cookieName) {
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }


}


세션 관리 WHEN ?

세션 CREATE은 유저가 로그인할 때 실행

세션 EXPIRE은 유저가 로그아웃하는 상황에 실행

  • 리프레쉬 토큰 만료
  • 로그아웃 버튼 누를 시

redis 연동이랑 이를 내 플젝에 적절히 커스터마이징하고 연결하는거 넘 길어서 다음편에다가 씀 😣

0개의 댓글