@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 |