3-1 세션과 필터
웹은 기본적으로 과거의 상태를 유지하지 않는 무상태(stateless)연결입니다. 요청과 응답을 하나의 단위로 처리하면서 기존 사용자에 대한 정보는 기억하지 않습니다. 무상태라는 특징으로 인해 기존의 방문자를 기억하기 위해서는 특별한 메커니즘을 사용하게 되는데 세션(HttpSession)이나 쿠키(Cookie)라는 존재를 이용하기도 하고 특정한 문자(토큰)을 이용하기도 합니다.
세션 트랙킹(session tracking): 로그인 유지를 위한 모든 기능
무상태에서 과거를 기억하는 법
HTTP는 기본적으로 무상태이므로 과거의 요청 기록을 알 수 없습니다. HTTP가 무상태를 선택한 가장 큰 이유는 역시 적은 자원으로 여러 요청을 처리할 수 있다는 장점이 있기 때문이지만 덕분에 과거의 방문 기록을 추적하는 기법이 필요하게 됩니다. 이러한 기법들을 세션 트랙킹이라고 합니다.
쿠키는 문자열로 되어있는 정보로 가장 기본적인 형태는 '이름(name)'과 '값(value)'의 구조입니다. 쿠키를 주고 받는 기본적인 시나리오는 다음과 같습니다.
- 브라우저에서 최초로 서버를 호출하는 경우에 해당 서버에서 발행한 쿠키가 없다면 브라우저는 아무것도 전송하지 않습니다.
- 서버에서는 응답메시지를 보낼 때 브라우저에게 쿠키를 보내주는데 이때 'Set-Cookie'라는 HTTP헤더를 이용합니다.
- 브라우저는 쿠키를 받은 후에 이에 대한 정보를 읽고, 이를 파일 형태로 보관할 것인지 메모리상에서만 처리할 것인지를 결정합니다. 이 판단은 쿠키에 있는 "유효기간(만료기간)"을 보고 판단합니다.
- 브라우저가 보관하는 쿠키는 다음에 다시 브라우저가 서버에 요청할 때 HTTP헤더에 'Cookie'라는 헤더 이름과 함께 전달합니다.
- 서버에서는 필요에 따라서 브라우저가 보낸 쿠키를 읽고 이를 사용합니다.
쿠키를 생성하는 방법
1. 서버에서 자동으로 생성하는 쿠키: 응답 메시지를 작성할 때 정해진 쿠키가 없는 경우 자동으로 발행 - WAS에서 발행되며 이름은 WAS마다 고유한 이름을 사용해서 쿠키를 생성합니다. 톰캣은 'JSESSIONID'라는 이름을 발행합니다.
- 서버에서 발행하는 쿠키는 기본적으로 브라우저의 메모리상에 보관합니다. 그렇기 때문에 브라우저를 종료하면 서버에서 발행한 쿠키는 삭제됩니다.
- 서버에서 발행하는 쿠키의 경로는 '/'로 지정됩니다.
2. 개발자가 생성하는 쿠키: 개발자가 생성하는 쿠키는 서버에서 생성되는 쿠키와 다음과 같은 점들이 다릅니다.
- 이름을 원하는대로 지정할 수 있습니다.
- 유효기간을 지정할 수 있습니다.
- 반드시 직접 응답에 추가해 주어야 합니다.
- 경로나 도메인 등을 지정할 수 있습니다.
서블릿 컨텍스트와 세션 저장소
서블릿 컨텍스트: 각각의 웹 애플리케이션은 자신만이 사용하는 고유의 메모리 영역을 하나 생성해서 이 공간에 서블릿이나 JSP 등을 인스턴스로 만들어 서비스를 제공합니다. 이 영역을 서블릿 API에서는 서블릿 컨텍스트라고 합니다.
세션 저장소: 각각의 웹 애플리케이션을 생성할 때는 톰캣이 발행하는 쿠키들을 관리하기 위한 메모리 영역이 하나 더 생성되는데 이 영역을 세션 저장소라고 합니다.
세션 저장소는 기본적으로 '키(key)'와 '값(value)'을 보관하는 구조입니다. 이때 키가 되는 역할을 하는 것이 톰캣에서 JSESSIONID라는 쿠키의 값입니다.
톰캣 내부의 세션 저장소는 발행된 쿠키들의 정보를 보관하는 역할을 하게 되는데 문제는 새로운 JSESSIONID 쿠키가 만들어 질때마다 메모리 공간을 차지해야 한다는 점입니다. 이 문제를 해결하기 위해서 톰캣은 주기적으로 세션 저장소를 조사하면서 더 이상 사용하지 않는 값들을 정리하는 방식으로 동작합니다.
HttpServletRequest의 getSession()
HttpServletRequest의 getSession()은 브라우저가 보내는 정보를 이용해서 다음과 같은 작업을 수행합니다.
- JSESSIONID가 없는 경우: 세션 저장소에 새로운 번호로 공간을 만들고 해당 공간에 접근할 수 있는 객체를 반환. 새로운 번호는 브라우저에 JSESSIONID의 값으로 전송
- JSESSIONID가 있는 경우: 세션 저장소에서 JSESSIONID 값을 이용해서 할당된 공간을 찾고 이 공간에 접글할 수 있는 객체를 반환
getSession()의 결과물은 세션 저장소 내의 공간인데 이 공간을 의미하는 타입은 HttpSession타입이라고 하며 해당 공간은 세션 컨택스트(Session Context) 혹은 세션(Session)이라고 합니다.
HttpSession 타입의 객체를 이용하면 현재 사용자만의 공간에 원하는 객체를 저장하거나 수정/삭제할 수 있습니다. 또한 isNew()와 같은 메소드로 새롭게 공간을 만들어 낸것인지 기존의 공간을 재사용하는지를 구분할 수 있습니다.
필터를 이용한 로그인 체크
로그인 여부를 체그해야하는 컨트롤러마다 동일하게 체크하는 로직을 작성하면 같은 코드를 계속 작성해야 하기 때문에 대부분은 필터(Server Filter)라는 것을 이용해서 처리합니다.
필터는 서블릿이나 JSP 등에 도달하는 과정에서 필터링하는 역할을 위해서 존재하는 서블릿 API의 특별한 객체입니다. @WebFilter 어노테이션을 이용해서 특정한 경로에 접근할 때 필터가 동작하도록 설계하면 동일한 로직을 필터로 분리할 수 있습니다. 필터는 1개 혹은 여러개를 적용할 수 있습니다.
3-2 사용자 정의 쿠키(cookie)
사용자가 정의하는 쿠키와 서버에서 자동으로 발행되는 쿠키(JSESSIONID)와 비교하면 다음과 같은 점들이 다릅니다.
사용자 정의 쿠키 | WAS에서 발행하는 쿠키(세션 쿠키) | |
생성 | 개발자가 직접 newCookie()로 생성 경로도 지정 가능 |
자동 |
전송 | 반드시 HttpServletResponse에 addCookie()를 통해야만 전송 | |
유효기간 | 쿠키 생성할 때 초 단위로 지정할 수 있음 | 지정불가 |
브라우저의 보관방식 | 유효기간이 없는 경우에는 메모리상에만 보관 유효기간이 있는 경우에는 파일이나 기타 방식으로 보관 |
메모리상에만 보관 |
쿠키의 크기 | 4kb | 4kb |
개발자가 직접 쿠키를 생성할 때는 newCookie()를 이용해서 생성합니다. 이때 반드시 문자열로 된 이름과 값이 필요합니다. 값은 일반적인 문자열로 저장이 불가능하기 때문에 URLEncoding된 문자열로 저장해야 합니다.
쿠키를 사용하는 경우
쿠키는 서버와 브라우저 사이를 오가기 때문에 보안에 취약한 단점이 있습니다. 이 때문에 상당히 제한적입니다.
'최근 본 상품 목록'과 '오늘 하루 이 창 열지 않기'같이 조금은 사소하고 서버에서 보관할 필요가 없는 데이터들은 쿠키를 이용해서 처리됩니다.
쿠키의 위상이 변하게 된 가장 큰 이유는 모바일에서 시작된 '자동 로그인'입니다. 쿠키의 유효기간을 지정하는 경우 브라우저가 종료되더라도 보관되는 방식으로 동작하게 되는데 모바일에서는 매번 로그인하는 수고로움을 덜어줍니다.
쿠키와 세션을 같이 활용하기
쿠키를 이용한 자동로그인은 'remember-me'라는 이름으로 부르기도 하는데 로그인한 사용자의 정보를 쿠키에 보관하고 이를이용해서 사용자의 정보를 HttpSession에 담는 방식입니다.
자동 로그인을 위해서는 쿠키에 어떤 값을 보관하게 할 것인지를 결정해야 하고 이 값의 유효시간도 고려해야 합니다.
로그인은 다음과 같은 방식으로 구현합니다.
- 사용자가 로그인할 때 임의의 문자열을 생성하고 이를 데이터베이스에 보관
- 쿠키에는 생성된 문자열을 값으로 삼고 유효기간은 1주일로 지정
로그인 체크는 다음과 같은 방식으로 구현합니다.
- 현재 사용자의 HttpSession에 로그인 정보가 없는 경우에만 쿠키를 확인
- 쿠키의 값과 데이터베이스의 값을 비교하고 같다면 사용자의 정보를 읽어와서 HttpSession에 사용자 정보를 추가합니다.
앞선 방식의 경우 쿠키의 값을 탈취당하면 문제가 발생할 수 있기 때문에 좀더 안전하게 하기 위해서는 주기적으로 쿠키의 값을 갱신하는 부분이 추가되어야만 합니다만 우선은 UUID를 이용한 임의의 문자열을 이용하도록 합니다. UUID(universally unique identifier)는 범용 고유 식별자로 고유한 번호를 랜덤으로 생성할 때 많이 사용합니다.
3-3 리스너
서블릿 API에는 리스너(Listener)라는 이름이 붙은 특별한 인터페이스들이 있습니다. 리스너 객체들은 이벤트(Event)라는 특정한 데이터가 발생하면 자동으로 실행되는 특징이 있습니다. 리스너를 이용하면 어떤 정보가 발생(event)했을 때 미리 약속해둔 동작을 수행할 수 있어 기존의 코드를 변경하지 않고도 추가적인 기능을 수행핼 수 있습니다.
리스너의 개념과 용도
프로그램을 작성하다 보면 어떤 작업의 영향으로 다른 작업이 같이 실행되어야 하는 경우가 있습니다.
예: 현재 서버에 접속한 모든 사용자의 IP를 로그로 남겨야 하는 경우에 가장 무식한 방법은 모든 컨트롤러의 코드를 열어서 d0Get()/doPost()에 로그를 기록하는 것입니다.
이러한 문제를 해결하기 위해 사용하는 패턴이 '옵저버(observer)'패턴입니다. 옵저버 패턴은 특정한 변화를 구독하는 객체들을 보관하고 있다가 변화가 발생하면 구독 객체들을 실행하는 방식입니다.
서블릿API는 여러 이벤트에 맞는 리스너들을 인터페이스들로 정의해 두었는데 이를 이용해 다음과 같은 작업을 처리할 수 있습니다.
- 해당 웹 애플리케이션이 시작되거나 종료될 때 특정한 작업을 수행
- HttpSession에 특정한 작업에 대한 감시와 처리
- HttpServletRequest에 특정한 작업에 대한 감시와 처리
'자바웹개발 워크북' 카테고리의 다른 글
자바웹개발 워크북(6) (1) | 2025.01.20 |
---|---|
자바웹개발 워크북(5) (0) | 2025.01.16 |
자바웹개발 워크북(4) (0) | 2025.01.05 |
자바웹개발 워크북(2) (0) | 2025.01.01 |
자바웹개발 워크북(1) (1) | 2024.12.30 |