DB/Redis

Redis를 이용한 세션 구현

Code Maestro 2023. 8. 10. 19:52
728x90

1. 세션이란?

세션연결에 필요한 메타 데이터 + 시간을 뜻하고, 어디에 적용되느냐에 따라 조금씩 다른 뜻을 가진다.

 

일반적인 세션은 네트워크 상에서 두 개 이상의 통신장치 간에 유지되는 상호 연결을 뜻한다. 연결된 일정 시간 동안의 유지 정보를 나타낸다. 

 

Web 로그인 세션웹에서 특정 유저가 로그인했음을 나타내는 정보이다. 브라우저는 Cookie를 서버는 해당 Cookie의 세션 정보를 저장한다. 유저가 로그아웃하거나 세션이 만료될 때까지 해당 유저의 서비스가 가능하다. 

 

1.1 로그인 과정

  1. 먼저 ID, Password를 입력해서 DB로부터 유저 정보가 일치한 지 확인한다.
  2. 일치하면 유저 정보를 세션으로 저장하고 연관된 SessionId를 반환한다. 
  3. Session ID를 HTTP Header의 set-Cookie 속성을 이용해 저장한다.
  4. Client에서 서버로 요청을 보낼 때마다 HTTP의 헤더에 SessionID를 저장한다.
  5. SessionId에 연관된 세션을 찾아 유저를 식별한다.

1.2 분산 환경에서 세션 처리

서버는 세션 정보를 저장해야 한다. 서버가 여러 대면 최초 로그인한 서버가 아닌 서버는 세션 정보를 알지 못한다. 그렇기에 세션 정보를 서버 간에 공유할 방법을 찾아야 한다.

 

서버들은 세션을 어떻게 공유할까? 

 

1) RDB 사용

관계형 데이터 모델이 필요하거나, 영속성이 필요한 데이터의 경우 보통 MySQL 같은 RDB를 사용한다. 

 

2) Redis 사용

세션 데이터는 단순 key-value 구조이고, 영속성이 필요 없다. 여기에 세션 데이터는 변경이 많고 빠른 액세스 속도가 필요하다. 

 

세션로그인이 유지될 때만 유지되면 되기에 RDB 제공하는 수준의 영속성과 안정성이 필요가 . 그렇기에 Redis가 안성맞춤이다. 

 

 

2. Spring Boot에서 세션 관리

세션 관리에 있어서 서버의 역할은 3가지가 있다. 첫 번째는 세션을 생성할 때, 요청이 들어오면 세션이 없다면 만들어서 응답에 set-cookie로 넘겨줘야 한다. 두 번째는 세션을 이용할 때, 세션이 있으면 해당 세션의 데이터를 가져와야 한다. 세 번째는 세션을 삭제할 때, 타임아웃이나 로그아웃 API를 통해 세션을 무효화 시켜야 한다. 

 

HttpSession은 세션을 손쉽게 생성하고 관리할 수 있게 해주는 인터페이스로 UUID로 세션 ID를 생성하고, JSESSIONID라는 이름의 cookie를 설정해 내려준다. 자세한 내용은 스프링 부분이라 생략하겠다. 

 

 

이제 HttpSession을 통해 로그인을 구현해 보자. 먼저 Java로 간단한 Login API를 생성하였고, 먼저 HashMap을 이용해 세션을 구현해 봤다.

@RestController
public class LoginController {

    HashMap<String, String> sessionMap = new HashMap<>();

    @GetMapping("/login")
    public String login(HttpSession session,@RequestParam String name){
        sessionMap.put(session.getId(),name);

        return "saved.";
    }

    @GetMapping("/myName")
    public String myName(HttpSession session){
        String myName = sessionMap.get(session.getId());

        return myName;
    }
}

 

'login?name=apple url'에 들어가서 세션을 저장하고 브라우저의 개발자 도구를 통해 Cookie를 확인해 보자. 

/myName으로 들어가서 아까 받았던 SessionID를 확인해 보면?

세션 ID가 일치하는 걸 확인할 수 있다. 

 

 

이러면 분산 환경에서 문제점은 없을까? 

 

 

하나는 8080 포트, 또 다른 실행은 8081 포트에서 해봤다.

 

먼저 8080 포트에서 저장한 세션 ID는 위와 같다. 

 

myName에서 확인해 보면 세션 ID가 동일하다는 걸 확인할 수 있다. 

 

 

8081 포트에서 /myName URL로 세션을 확인해 보면 

다른 인스턴스로 호출이 된다. 그 인스턴스는 모르는 SessionID를 가지고 호출이 된다.

 

동일한 세션 ID가 들어오기 하는데 받아주는 서버가 다른 상황이 만들어진다. 왜냐면 8081 포트의 서버는 그 세션 ID로 저장한 데이터가 없기에 응답 헤더에서 '26B73C8~~' 새로운 ID가 나온다. 이는 서버에서 새로운 Session ID로 set을 해서 덮어 씌었기 때문이다. 

 

그래서 로그인을 하기 위해서는 별도로 저 세션 ID를 가지고 해줘야 한다. 

 

localhost:8081/login?name=apple 로 로그인을 해준다.

 

 

이후의 요청은 이 세션 ID를 가지고 진행이 된다.

 

 

이렇게 2대 이상의 WAS 서버에는 세션이 공유가 되지 않는다. 이는 세션 저장소로 아무 설정을 안 하면 별도의 내장 톰캣의 메모리에 저장이 되기 때문이다. (8080 포트 톰캣 메모리, 8081 포트 톰캣 메모리)

 

또 다른 문제점은 메모리에 저장돼서 배포할 때마다 톰캣이 재시작하기에 WAS 실행마다 세션이 초기화가 된다.

 

3. Redis를 활용한 세션 클러스터링

2번에서의 문제점을 해결하기 위해 위의 그림처럼 Redis에 세션을 저장해 보겠다.

 

application.yml 파일에

spring:
  session:
    storage-type:redis
  redis:
    host: localhost
    port: 6379

세션 저장소를 redis로 설정한다.

 

@RestController
public class LoginController {


    @GetMapping("/login")
    public String login(HttpSession session,@RequestParam String name){
        session.setAttribute(session.getId(),name);

        return "saved.";
    }

    @GetMapping("/myName")
    public String myName(HttpSession session){
        String myName = (String)session.getAttribute(session.getId());

        return myName;
    }
}

Java 코드를 위처럼 변경한다.

 

이제 Session이 관리하는 storage는 yml 파일에 지정한 redis가 된다. 여러 개의 인스턴스가 실행돼도 동일한 세션 ID를 가지고, 동작할 수 있게 된다.

 

세션 저장을 redis에 하기 위해 gradle에서 해당 라이브러리의 의존성을 추가해 준다.

 

아까처럼 실험을 해보면

 

8081 포트와 8080 포트 브라우저의 쿠키를 확인해 보면,세션 ID 모두 동일한 걸 확인할 수 있다. 

728x90