세션도 이전에 정리한 포스트가 있기 때문에 여기서는 간단하게 다룹니다.
세션(session)
은 서버측의 메모리에 데이터가 저장됩니다. 지난 포스트에서 다뤘던 쿠키
는 클라이언트측에 저장되기 때문에 보안에 취약했지만 세션
은 서버측에 저장되기 때문에 쿠키보다 보안성이 뛰어납니다. 그래서 로그인 정보와 같은 보안성이 요구되는 데이터는 세션을 통해서 데이터가 저장됩니다.
세션
은 하나의 브라우저 당 하나의 세션이 생성됩니다. 그래서 네이버같은 페이지에서 로그인을 한 뒤 같은 새 탭 열기나 페이지 이동과 같은 행동을 해도 로그인 정보가 유지됩니다.
세션
의 특징은 다음과 같습니다.
서버는 고유한 세션 ID를 통해 세션을 구별하게 됩니다.
1. 웹 페이지에 접속합니디.
2. 서버는 접속한 브라우저에 대한 세션 객체 하나를 생성합니다.
3. 생성된 세션 ID를 클라이언트로 전송합니다.
4. 클라이언트측 브라우저는 받은 세션 ID를 세션 쿠키에 저장합니다. (브라우저 메모리에 저장)
실습하고 있는 톰캣 서버의 경우 세션 ID를 저장하는 쿠키의 이름은
jsessionId
입니다.
1. 재접속 시, 브라우저는 브라우저 메모리에 있던 세션 쿠키의 세션 ID를 서버로 전달합니다.
2. 서버는 전달받은 세션 ID를 통해 세션에 접근하여 작업을 수행합니다.
이제 세션의 개념과 동작을 알았으니 서블릿을 이용해서 세션을 사용하는 방법에 대해 실습해보겠습니다.
세션을 이용하기 위해서는 jakarta.servlet.http.HttpSession 클래스
를 사용합니다. HttpSession 클래스
에서는 세션에 대한 다양한 설정 메소드들을 제공하고 있습니다.
다음은 HttpSession 클래스
에서 제공하는 주요 메소드들입니다.
메소드 | 설명 |
---|---|
Object getAttribute(String name) | 세션 속성 이름이 name인 속성 값을 Object 타입으로 반환. 없으면 null 반환. |
Enumeration getAttribute() | 세션 속성 이름들을 Enumeration 타입으로 반환 |
void removeAttribute(String name) | 세션 속성 이름이 name인 속성 제거 |
void setAttribute(String name, Object value) | 세션 속성 이름이 name인 속성에 속성 값으로 value를 설정 |
int getMaxInactiveInterval() | 설정된 세션 유지 시간을 int 타입으로 반환 |
void setMaxInactiveInterval(int interval) | 세션 유지 시간을 설정. (단위는 초) |
long getCreationTime() | 유닉스 시간(1970년 1월 1일 0시 0분 0초)를 기준으로 현재 세션이 생성된 시간 경과를 계산하여 밀리초(1/1000초)로 반환 |
String getId() | 세션에 할당된 세션 ID를 String 타입으로 반환 |
void invalidate() | 현재 생성된 세션을 소멸 |
boolean isNew() | 최초로 생성된 세션인지 기존에 생성된 세션인지를 판별하여 반환 |
HttpSession 객체
는 HttpServletRequest.getSession()
메소드를 호출해서 생성하게 됩니다. 이때 getSession()
의 인수로 boolean
값을 전달할 수 있는데 전달한 인수에 대한 동작은 다음과 같습니다.
getSession()
: 기존 세션 객체가 존재하면 반환하고, 존재하지 않으면 새로 생성getSession(true)
: 기존 세션 객체가 존재하면 반환하고, 존재하지 않으면 새로 생성 (아무것도 전달하지 않는 경우와 동일)getSession(false)
: 기존 세션 객체가 존재하면 반환하고, 존재하지 않으면 null
반환 그럼 실습을 통해 세션을 생성하고 확인해보겠습니다. 실습용 코드인 SessionServlet.java
를 생성하고 다음과 같이 작성해주세요.
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
@WebServlet("/session")
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
//세션 취득. 새로 생성하거나, 기존 세션을 가져옴
HttpSession session = req.getSession();
out.print("부여된 세션 ID: " + session.getId() + "<br/>");
Date sessionCreationTime = new Date(session.getCreationTime());
out.print("세션 최초 생성 시간: " + sessionCreationTime + "<br/>");
Date lastAccessedTime = new Date(session.getLastAccessedTime());
out.print("최근 세션 접근 시간: " + lastAccessedTime + "<br/>");
}
}
실행 후 http://localhost:8090/session
로 접속합니다.
새 탭을 열어서 똑같은 주소로 접속을 합니다. 동일 브라우저에서 접속을 했기 때문에 세션 ID나 최초 생성 시간이 동일해야겠죠?이번엔 다른 브라우저를 열거나 시크릿 모드를 통해 접속합니다. 다른 브라우저이기 때문에 세션 ID가 새로 생성될 것 입니다.
참고로 setMaxInactiveInterval()
을 통해 세션의 유지 시간을 초 단위로 설정할 수 있는데요. 아무것도 설정하지 않았을 때는 톰캣 기준으로 30분(1800초)
가 기본적으로 설정되어 있습니다.
이제 여러 서블릿이 정보를 공유하도록 세션 트래킹을 구현해보겠습니다. 세션으로 구현할 수 있는 것들 중 가장 흔한 로그인을 예제로 사용하도록 하겠습니다.
먼저, 톰캣 설정상 세션이 메모리에서 삭제되지 않는 경우가 있지 때문에 톰캣 설정 파일인
context.xml
에서 다음 부분을 주석 해제해주세요. 참조<Manager pathname="" />
지난번에 만들어 놓은 로그인 폼을 재활용하겠습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form name="loginForm" method="post" action="sessionLogin" enctype="UTP-8">
<fieldset>
<legend>Login</legend>
<label>아이디: </label>
<input type="text" name="id"><br/>
<label>비밀번호: </label>
<input type="text" name="password"><br/>
<input type="submit" value="login">
<input type="hidden" name="phoneNumber" value="000-0000-0000">
</fieldset>
</form>
</body>
</html>
다음으론 세션을 생성하고 로그인 정보를 바인딩할 SessionLoginServlet.java
를 작성합니다.
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/sessionLogin")
public class sessionLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doHandle(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doHandle(req, resp);
}
private void doHandle(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
String id = req.getParameter("id");
String password = req.getParameter("password");
if (session.isNew()) {
//세션이 새로 생성되면 세션에 id를 바인딩
session.setAttribute("id", id);
out.print("<a href='sessionLogin'>로그인 확인</a>");
}
else {
id = (String) session.getAttribute("id");
out.print("로그인한 사용자: " + id);
}
}
}
if (session.isNew())
부분이 필요한데요. login.html에서 서블릿으로 요청을 하고 세션이 생성된 순간은 세션에 로그인 정보가 바인딩되어있지 않습니다. 그래서 <a>
를 통해 바인딩을 하게 만들어줍니다. 실제로도 우리가 로그인을 하면 새로고침이나 페이지 이동을 해서 로그인이 된 상태로 바뀌는 것을 생각해보세요.
<a>
태그가 다시 /sessionLogin
을 가리키기 때문에 새로고침과 같은 효과를 주게 되고, 새로운 세션이 아니기 때문에 id에 로그인 폼에서 전송된 id가 바인딩 되어서 결과가 나타나게 되는 것입니다. 만약 바인딩이 제대로 되지 않았다면 null
이 나타납니다.