스프링/4. 스프링 MVC-2

49. 서버세션 구현

sdafdq 2023. 9. 6. 00:29
@Component
public class SessionManager {
    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    public void createSession(Object value, HttpServletResponse response){
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);
        Cookie cookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(cookie);
    }

    public Object getObjectFromSession(HttpServletRequest request){
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);

        if(sessionCookie == null){
            return null;
        }

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

    public Cookie findCookie(HttpServletRequest request, String cookieName){
        Cookie[] cookies = request.getCookies();

        if(cookies ==null){
            return null;
        }

        return Arrays.stream(cookies)
                .filter(c -> c.getName().equals(cookieName))
                .findAny().orElse(null);
    }

    public void expire(HttpServletRequest request){
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if(sessionCookie != null){
            sessionStore.remove(sessionCookie.getValue());
        }
    }
}

역시나 간단하게 메모리에 담을 콜렉션, 근데 이번 같은 경우는, 드디어 동시성 문제에 면역인 ConcurrentHashMap를 쓴다.

동시성 문제에 면역인 이유는, 이 콜렉션에 대한 접근은 내부적으로 동기적이기 때문이다.

그리고 ConcurrentHashMap는 key든 value든 null을 허용하지 않는다.

 

SESSION_COOKIE_NAME 이렇게 정의한 이유는 자주 사용하기 때문이다.

 

첫번째 세션 생성은

UUID로 임의의 UUID 객체를 생성한 다음 String으로 변환하였다.

UUID의 UU는 Universally Uniq, 굉장히 넓은, 범용적으로, 유니크한 이라는 뜻이다.

 

세션 저장소에 이 UUID를 키로, Object를(외부에서 멤버저장소에서 찾아서 Member로 넣어줄거다) 값으로 넣는다.

응답에 

SESSION_COOKIE_NAME를 키로, UUID를 값으로 쿠키를 만들어 넣어준다.

 

이러면 이제 Cookie에 별다른 설정을 하지 않았으니, 웹브라우저가 종료되기 전 까지 계속 쿠키를 유지하며 클라이언트가 서버에 보내줄 것이다.

 

 

 

Member(Object)를 찾는 것은 간단하다.

들어오는 응답의 SESSION_COOKIE_NAME 를 키로 한 쿠키를 찾아서, 

그걸 세션저장소에서 조회해 보면 된다.

참고로, 없을 경우 그냥 null이 반환된다.

 

 

findCookie는 쿠키가 뽑아낼 때 좀 불편한 게, 배열로 뽑을 수밖에 없다.

 

Arrays.stream(배열)하면, 배열을 stream으로 바꿔줘, filter()를 쓸 수 있다.

여튼 그래서, SESSION_COOKIE_NAME 의 이름(키)를 가진 쿠키를 찾아내어 반환한다.

 

 

쿠키 만료는 쉽다.

요청에서 쿠키를 찾아 만약 세션쿠키가 있다면 서버의 세션 저장소에서 지워버린다.

 

 

 

 

public class SessionManagerTest {
    SessionManager sessionManager = new SessionManager();

    @Test
    void sessionTest(){
        Member member = new Member();
        MockHttpServletResponse response = new MockHttpServletResponse();
        sessionManager.createSession(member, response);

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setCookies(response.getCookies());

        Object foundMember = sessionManager.getObjectFromSession(request);

        assertThat(member).isSameAs(foundMember);

        sessionManager.expire(request);

        Object expired = sessionManager.getObjectFromSession(request);
        assertThat(expired).isNull();
    }

}

테스트.

MockHttpServletResponse 이거는 가상의 HttpServletResponse를 만들어 주는거다. 테스트를 위해 스프링이 제공해주는 기능이다. request도 마찬가지이다.

이 response에서는 특별히 cookie를 뽑아낼 수 있다. (그냥 HttpServletResponse에서는 안된다.)

 

여튼, 멤버를 만들고, 세션을 생성한다.

 

그 다음, response에 들어간 쿠키를 뽑아내어 request에 넣고, 그 request로 sessionManager에 SessionId로 멤버객체를 찾아오라고 명령한다.

 

그 멤버가, 내가 처음에 생성한 멤버와 같은 지 확인한다.

 

그 다음, 그 세션을 파괴한다.

세션만료 함수에 request를 인자로 주면, request에서 sessionId를 추출해서 세션 저장소에서 제거한다.

 

그 다음, 다시 request를 줘서 SessionId로 세션 저장소에서 Member를 찾아오도록 해보고,

그것이 Null이면 잘 제거 된 것이다.

 

 

'스프링 > 4. 스프링 MVC-2' 카테고리의 다른 글

51. 스프링의 Http 세션  (0) 2023.09.07
50. 서버세션 적용  (0) 2023.09.06
48. 서버 세션  (0) 2023.09.05
47. 쿠키의 보안문제  (0) 2023.09.05
46. 쿠키를 이용한 로그인 구현.  (0) 2023.09.05