자바에서의 세션 관리는 서블릿 웹 애플리케이션에서 매우 흥미로운 주제입니다. 자바 서블릿에서의 세션은 쿠키, HttpSession API, URL 재작성 등과 같은 다양한 방법으로 관리됩니다. 이것은 자바 웹 애플리케이션 튜토리얼 시리즈의 세 번째 기사입니다. 이전 두 기사도 확인하실 수 있습니다.
자바에서의 세션 관리
이 기사는 서블릿에서 다양한 기술을 사용하여 세션 관리에 대해 설명하고 예제 프로그램을 제공하는 것을 목표로 합니다.
-
세션이란?
HTTP 프로토콜과 웹 서버는 상태를 유지하지 않는데, 이것은 웹 서버에 대해 모든 요청이 처리되는 새로운 요청이며, 그것이 이전에 요청을 보낸 클라이언트인지 여부를 식별할 수 없다는 것을 의미합니다. 그러나 때로는 웹 애플리케이션에서 클라이언트가 누구인지 알아야 하고 요청을 그에 맞게 처리해야 합니다. 예를 들어, 쇼핑 카트 애플리케이션은 누가 항목을 추가하는 요청을 보내고 해당 항목을 어느 카트에 추가해야 하는지 또는 체크아웃 요청을 보내는 사람은 누구인지 알아야 합니다. 그래서 올바른 클라이언트에게 금액을 청구할 수 있습니다. 세션은 클라이언트와 서버 간의 대화 상태이며, 클라이언트와 서버 간의 여러 요청과 응답으로 구성될 수 있습니다. HTTP와 웹 서버 모두 상태를 유지하지 않으므로 세션을 유지하는 유일한 방법은 세션에 대한 고유한 정보(세션 ID)가 모든 요청과 응답 사이에 서버와 클라이언트 간에 전달될 때입니다. 요청과 응답에서 고유 식별자를 제공하는 여러 가지 방법이 있습니다.
-
사용자 인증 – 이는 매우 일반적인 방법으로 사용자가 로그인 페이지에서 인증 자격 증명을 제공하고 서버와 클라이언트 간에 인증 정보를 전달하여 세션을 유지할 수 있습니다. 이 방법은 동일한 사용자가 다른 브라우저에서 로그인한 경우에는 작동하지 않으므로 효과적인 방법이 아닙니다.
-
HTML 숨은 필드 – HTML에서 고유한 숨은 필드를 생성할 수 있으며 사용자가 탐색을 시작할 때 해당 값에 사용자별로 고유한 값을 설정하고 세션을 추적할 수 있습니다. 이 방법은 링크와 함께 사용할 수 없으므로 클라이언트에서 서버로 요청을 보낼 때마다 양식을 제출해야 합니다. 또한 HTML 소스에서 숨은 필드 값을 가져와 세션을 해킹하는 데 사용할 수 있으므로 안전하지 않습니다.
-
URL 재작성 – 모든 요청과 응답에 세션 식별자 매개변수를 추가하여 세션을 추적할 수 있습니다. 이 방법은 매우 번거롭기 때문에 모든 응답에서 이 매개변수를 추적하고 다른 매개변수와 충돌하지 않도록해야 합니다.
-
쿠키 – 쿠키는 웹 서버에서 응답 헤더로 보내지는 작은 정보 조각이며 브라우저 쿠키에 저장됩니다. 클라이언트가 추가 요청을 할 때, 쿠키를 요청 헤더에 추가하고 세션을 추적하는 데 사용할 수 있습니다. 쿠키를 사용하여 세션을 유지할 수 있지만 클라이언트가 쿠키를 비활성화하면 작동하지 않습니다.
-
세션 관리 API – 세션 관리 API는 세션 추적을 위한 위의 방법을 기반으로 구축되었습니다. 위의 모든 방법의 주요 단점은 다음과 같습니다:
- 대부분의 경우 세션만 추적하고 싶은 것이 아니라 나중에 요청에서 사용할 수 있는 데이터를 세션에 저장해야 합니다. 이를 구현하려면 많은 노력이 필요합니다.
- 위의 모든 방법은 자체적으로 완전하지 않으며 특정 시나리오에서는 모두 작동하지 않습니다. 그래서 모든 경우에 세션 관리를 제공하기 위해 세션 추적 방법을 활용할 수 있는 솔루션이 필요합니다.
이것이 바로 우리가 세션 관리 API가 필요한 이유이며 J2EE 서블릿 기술은 세션 관리 API를 제공합니다.
-
-
자바에서의 세션 관리 – 쿠키
쿠키는 웹 애플리케이션에서 많이 사용되어 선택에 따라 응답을 개인화하거나 세션을 추적하는 데 사용됩니다. 서블릿 세션 관리 API로 전환하기 전에 작은 웹 애플리케이션을 통해 쿠키를 사용하여 세션을 추적하는 방법을 보여드리겠습니다. 아래 이미지와 같은 프로젝트 구조로 동적 웹 애플리케이션 ServletCookieExample을 만들 것입니다. 웹 애플리케이션의 배치 기술자인 web.xml은 다음과 같습니다:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://java.sun.com/xml/ns/javaee" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>ServletCookieExample</display-name> <welcome-file-list> <welcome-file>login.html</welcome-file> </welcome-file-list> </web-app>
우리 애플리케이션의 환영 페이지는 사용자로부터 인증 세부 정보를 받을 login.html입니다.
<!DOCTYPE html> <html> <head> <meta charset="US-ASCII"> <title>로그인 페이지</title> </head> <body> <form action="LoginServlet" method="post"> 사용자 이름: <input type="text" name="user"> <br> 비밀번호: <input type="password" name="pwd"> <br> <input type="submit" value="로그인"> </form> </body> </html>
로그인 요청을 처리하는 LoginServlet입니다.
package com.journaldev.servlet.session; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class LoginServlet */ @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; private final String userID = "Pankaj"; private final String password = "journaldev"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // get request parameters for userID and password String user = request.getParameter("user"); String pwd = request.getParameter("pwd"); if(userID.equals(user) && password.equals(pwd)){ Cookie loginCookie = new Cookie("user",user); //setting cookie to expiry in 30 mins loginCookie.setMaxAge(30*60); response.addCookie(loginCookie); response.sendRedirect("LoginSuccess.jsp"); }else{ RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html"); PrintWriter out= response.getWriter(); out.println("<font color=red>사용자 이름 또는 비밀번호가 잘못되었습니다.</font>"); rd.include(request, response); } } }
우리가 설정하는 쿠키와 그것을 LoginSuccess.jsp로 전달하는 것을 주목하십시오. 이 쿠키는 세션을 추적하는 데 사용될 것입니다. 또한 쿠키의 만료 시간이 30분으로 설정되어 있음을 주목하세요. 이상적으로 세션 추적을 위해 쿠키 값을 설정하는 복잡한 로직이 있어야 합니다. 그래야 다른 요청과 충돌하지 않습니다.
<%@ page language="java" contentType="text/html; charset=US-ASCII" pageEncoding="US-ASCII"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title>로그인 성공 페이지</title> </head> <body> <% String userName = null; Cookie[] cookies = request.getCookies(); if(cookies !=null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("user")) userName = cookie.getValue(); } } if(userName == null) response.sendRedirect("login.html"); %> <h3>안녕하세요 <%=userName %>, 로그인에 성공했습니다.</h3> <br> <form action="LogoutServlet" method="post"> <input type="submit" value="로그아웃" > </form> </body> </html>
JSP에 직접 액세스하려고 하면 로그인 페이지로 전달됩니다. 로그아웃 버튼을 클릭하면 클라이언트 브라우저에서 쿠키가 제거되었는지 확인해야 합니다.
package com.journaldev.servlet.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * Servlet implementation class LogoutServlet */ @WebServlet("/LogoutServlet") public class LogoutServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); Cookie loginCookie = null; Cookie[] cookies = request.getCookies(); if(cookies != null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("user")){ loginCookie = cookie; break; } } } if(loginCookie != null){ loginCookie.setMaxAge(0); response.addCookie(loginCookie); } response.sendRedirect("login.html"); } }
쿠키를 제거하는 방법은 없지만 만료 시간을 0으로 설정하여 클라이언트 브라우저에서 즉시 삭제할 수 있습니다. 위의 애플리케이션을 실행하면 아래 이미지와 같은 응답이 나옵니다.
- java
Java Servlet의 세션 – HttpSession
Servlet API는
HttpSession
인터페이스를 통해 세션 관리를 제공합니다. 다음 메서드를 사용하여 HttpServletRequest 객체에서 세션을 가져올 수 있습니다. HttpSession을 사용하면 향후 요청에서 검색할 수 있는 속성으로 객체를 설정할 수 있습니다.- HttpSession getSession() – 이 메서드는 항상 HttpSession 객체를 반환합니다. 요청에 세션이 첨부되어 있으면 해당 세션 객체를 반환하고, 세션이 첨부되지 않은 경우 새 세션을 만들어 반환합니다.
- HttpSession getSession(boolean flag) – 이 메서드는 요청에 세션이 있는 경우 HttpSession 객체를 반환하고, 그렇지 않은 경우 null을 반환합니다.
HttpSession의 중요한 메서드 중 일부는 다음과 같습니다:
- String getId() – 이 세션에 할당된 고유 식별자를 포함하는 문자열을 반환합니다.
- Object getAttribute(String name) – 이 세션에 지정된 이름으로 바인딩된 객체를 반환하거나 해당 이름으로 바인딩된 객체가 없는 경우 null을 반환합니다. 세션 속성을 처리하는 데 사용할 수 있는 일부 다른 메서드로는
getAttributeNames()
,removeAttribute(String name)
,setAttribute(String name, Object value)
등이 있습니다. - long getCreationTime() – 이 세션이 생성된 시간을 밀리초로 반환합니다(1970년 1월 1일 자정 GMT부터 측정).
getLastAccessedTime()
메서드로 마지막 액세스 시간을 얻을 수 있습니다. - setMaxInactiveInterval(int interval) – 서블릿 컨테이너가이 세션을 무효화하기 전에 클라이언트 요청 사이의 시간(초)을 지정합니다. 세션 타임아웃 값을
getMaxInactiveInterval()
메서드에서 가져올 수 있습니다. - ServletContext getServletContext() – 애플리케이션의 ServletContext 객체를 반환합니다.
- boolean isNew() – 클라이언트가 아직 세션에 대해 알지 못하거나 클라이언트가 세션에 참여하지 않기를 선택한 경우 true를 반환합니다.
- void invalidate() – 이 세션을 무효화하고 바인딩된 모든 객체를 해제합니다.
JSESSIONID 쿠키 이해하기
HttpServletRequest의 getSession() 메서드를 사용하여 새 요청을 생성하면 새 HttpSession 객체가 생성되고 응답 객체에 이름이 JSESSIONID이고 값이 세션 ID인 쿠키가 추가됩니다. 이 쿠키는 클라이언트로부터의 이후 요청에서 HttpSession 객체를 식별하는 데 사용됩니다. 클라이언트 측에서 쿠키가 비활성화되어 있고 URL 재작성을 사용하는 경우 이 메서드는 요청 URL에서 jsessionid 값을 사용하여 해당 세션을 찾습니다. JSESSIONID 쿠키는 세션 추적에 사용되므로 응용 프로그램 목적으로 사용하지 않아야 합니다. HttpSession 객체를 사용한 세션 관리 예제를 살펴보겠습니다. Eclipse에서 서블릿 컨텍스트를 ServletHttpSessionExample로 설정한 동적 웹 프로젝트를 만듭니다. 프로젝트 구조는 아래 이미지와 같이 보일 것입니다.
login.html은 이전 예제와 같으며 web.xml에서 응용 프로그램의 환영 페이지로 정의됩니다. LoginServlet 서블릿은 세션을 생성하고 다른 리소스나 향후 요청에서 사용할 수 있는 속성을 설정합니다.
package com.journaldev.servlet.session;
import java.io.IOException;
import java.io.PrintWriter;import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/**
* Servlet implementation class LoginServlet
*/
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final String userID = "admin";
private final String password = "password";protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {// userID와 password에 대한 요청 매개변수 가져오기
String user = request.getParameter("user");
String pwd = request.getParameter("pwd");if(userID.equals(user) && password.equals(pwd)){
HttpSession session = request.getSession();
session.setAttribute("user", "Pankaj");
// 세션 유효 시간을 30분으로 설정
session.setMaxInactiveInterval(30*60);
Cookie userName = new Cookie("user", user);
userName.setMaxAge(30*60);
response.addCookie(userName);
response.sendRedirect("LoginSuccess.jsp");
}else{
RequestDispatcher rd = getServletContext().getRequestDispatcher("/login -
Java Servlet에서의 세션 관리 – URL 재작성
지난 섹션에서 HttpSession으로 세션을 관리할 수 있다는 것을 보았지만 브라우저에서 쿠키를 비활성화하면 클라이언트에서 JSESSIONID 쿠키를 서버로 전달하지 않기 때문에 작동하지 않습니다. Servlet API는 URL 재작성을 지원하여 이 경우 세션을 관리할 수 있습니다. 코딩 관점에서 보면 매우 쉽게 사용할 수 있으며 하나의 단계만 포함됩니다 – URL 인코딩입니다. Servlet URL 인코딩의 좋은 점은 이것이 대체 접근법이라는 것이며 브라우저 쿠키가 비활성화된 경우에만 활성화되는 것입니다. HttpServletResponse
encodeURL()
메소드를 사용하여 URL을 인코딩할 수 있으며 요청을 다른 리소스로 리디렉션하고 세션 정보를 제공하려는 경우encodeRedirectURL()
메소드를 사용할 수 있습니다. 우리는 위와 유사한 프로젝트를 생성할 것이며 다만 브라우저에서 쿠키가 비활성화되어도 세션 관리가 제대로 작동하는지 확인하기 위해 URL 재작성 방법을 사용할 것입니다. Eclipse에서 ServletSessionURLRewriting 프로젝트 구조는 아래 이미지와 같습니다.package com.journaldev.servlet.session; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * Servlet implementation class LoginServlet */ @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; private final String userID = "admin"; private final String password = "password"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // get request parameters for userID and password String user = request.getParameter("user"); String pwd = request.getParameter("pwd"); if(userID.equals(user) && password.equals(pwd)){ HttpSession session = request.getSession(); session.setAttribute("user", "Pankaj"); //setting session to expiry in 30 mins session.setMaxInactiveInterval(30*60); Cookie userName = new Cookie("user", user); response.addCookie(userName); //Get the encoded URL string String encodedURL = response.encodeRedirectURL("LoginSuccess.jsp"); response.sendRedirect(encodedURL); }else{ RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html"); PrintWriter out= response.getWriter(); out.println("<font color=red>Either user name or password is wrong.</font>"); rd.include(request, response); } } }
<%@ page language="java" contentType="text/html; charset=US-ASCII" pageEncoding="US-ASCII"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title>로그인 성공 페이지</title> </head> <body> <% //세션이 존재하는 경우에만 액세스 허용 String user = null; if(session.getAttribute("user") == null){ response.sendRedirect("login.html"); }else user = (String) session.getAttribute("user"); String userName = null; String sessionID = null; Cookie[] cookies = request.getCookies(); if(cookies !=null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("user")) userName = cookie.getValue(); if(cookie.getName().equals("JSESSIONID")) sessionID = cookie.getValue(); } }else{ sessionID = session.getId(); } %> <h3>안녕하세요 <%=userName %>, 로그인 성공했습니다. 세션 ID=<%=sessionID %></h3> <br> 사용자=<%=user %> <br> <!-- 세션 정보가 전달되어야 하는 모든 URL을 인코딩해야 함 --> <a href="<%=response.encodeURL("CheckoutPage.jsp") %>">체크아웃 페이지</a> <form action="<%=response.encodeURL("LogoutServlet") %>" method="post"> <input type="submit" value="로그아웃" > </form> </body> </html>
<%@ page language="java" contentType="text/html; charset=US-ASCII" pageEncoding="US-ASCII"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title>로그인 성공 페이지</title> </head> <body> <% String userName = null; //세션이 존재하는 경우에만 액세스 허용 if(session.getAttribute("user") == null){ response.sendRedirect("login.html"); }else userName = (String) session.getAttribute("user"); String sessionID = null; Cookie[] cookies = request.getCookies(); if(cookies !=null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("user")) userName = cookie.getValue(); } } %> <h3>안녕하세요 <%=userName %>, 체크아웃하세요.</h3> <br> <form action="<%=response.encodeURL("LogoutServlet") %>" method="post"> <input type="submit" value="로그아웃" > </form> </body> </html>
package com.journaldev.servlet.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * Servlet implementation class LogoutServlet */ @WebServlet("/LogoutServlet") public class LogoutServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); Cookie[] cookies = request.getCookies(); if(cookies != null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("JSESSIONID")){ System.out.println("JSESSIONID="+cookie.getValue()); } cookie.setMaxAge(0); response.addCookie(cookie); } } //세션이 존재하는 경우 무효화 HttpSession session = request.getSession(false); System.out.println("User="+session.getAttribute("user")); if(session != null){ session.invalidate(); } //세션이 무효화되었으므로 인코딩하지 않음 response.sendRedirect("login.html"); } }
브라우저에서 쿠키를 비활성화한 채로 이 프로젝트를 실행하면 아래 이미지는 응답 페이지를 보여줍니다. 브라우저 주소 표시 줄의 URL에 jsessionid를 주목하십시오. 또한 로그인 성공 페이지에서 마지막 응답에서 보낸 쿠키를 브라우저가 전송하지 않았기 때문에 사용자 이름이 null임을 주목하십시오.
쿠키가 비활성화되지 않은 경우 Servlet 세션 API는 그 경우에 쿠키를 사용합니다.
자바 서블릿의 세션 관리에 관한 내용은 여기까지입니다. 앞으로는 Servlet 필터 및 리스너 및 쿠키를 살펴볼 것입니다. 업데이트: 시리즈의 다음 글을 확인하십시오 Servlet Filter.