bdfgdfg

서블릿에서의 쿠키와 세션 본문

웹프로그래밍/JSP_Servlet

서블릿에서의 쿠키와 세션

marmelo12 2023. 8. 10. 15:12
반응형

HTTP 프로토콜을 사용하는 웹 환경은 기본적으로 stateless 환경.

어느 한 웹페이지에서 로그인요청을하면 다른 웹페이지에서는 로그인 한 정보를 알 수 없기때문에 이를 공유할 수 있어야한다.

대표적인 방법

 쿠키 : 클라이언트 PC의 Cookie파일에 정보를 저장한 후 웹페이지들이 공유

 세션 : 서버 메모리에 정보를 저장한 후 웹페이지들이 공유

 

일일이 로그인 상태를 확인하기위해 로그인 정보를 다른 웹페이지에 전송하는 방식도 있지만, 비효율적.

 

쿠키를 이용한 웹 페이지 연동

쿠키란 클라이언트 PC의 Cookie파일에 정보를 저장한 후 웹페이지들이 공유하는 것이라고 했다.

 - 정보가 클라이언트 PC에 저장됨

 - 저장 정보 용량에 제한이 있음

 - 보안에 취약

 - 클라이언트 브라우저에서 사용 유무를 설정할 수 있음

 - 웹 사이트당 하나의 쿠키가 만들어짐

 

쿠키는 크게 2가지 종류로 나눌 수 있다.

쿠키의 생성과정은 다음과 같다.

 - 브라우저로 사이트에 접속하면 서버는 정보를 저장한 쿠키를 생성하고 클라이언트에 응답.

 

쿠키는 javax.servlet.http.Cookie를 이용하며 addCookie메소드를 이용해 클라이언트에 쿠키를 전송한다

또한 내부 메소드로 setMaxAge메소드에 인자값의 종류를 지정해서 Persistence쿠키와 Session쿠키를 구별해서 만들 수 있음.

 -> setMaxAge메소드를 이용하지않거나 음수를 전달하면 session쿠키. 양수값을 지정하면 Persistence쿠키

 

@WebServlet("/setCookie")
public class setCookieValue extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		Date d = new Date();
		
		// CookieTest라는 이름으로 프로그래밍이라는 정보를 한글로 인코딩해 쿠키에 저장.
		Cookie c = new Cookie("cookieTest",URLEncoder.encode("프로그래밍","utf-8"));
		c.setMaxAge(24 * 60 * 60 ); // persistence 쿠키.
		response.addCookie(c);
		
		System.out.println("현재시간 : " + d);
	}

}
@WebServlet("/getCookie")
public class GetCookieValue extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		Cookie[] allValues = request.getCookies();
		for(Cookie cookie : allValues)
		{
			if(cookie.getName().equals("cookieTest")) {
				out.println("<h2>쿠키 값 가져오기! : " + URLDecoder.decode(cookie.getValue(),"utf-8"));
			}
		}
	}

}

실행해보면

위 예제는 Persistence쿠키이며, 세션쿠키는 setMaxAge를 음수로 지정해서 세션 쿠키로 만들어주자.

 

세션을 이용한 웹 페이지 연동

쿠키와 개념은 같지만, 차이는 쿠키는 클라이언트 PC에 저장된다는점과 세션은 서버 메모리에 저장을 한다는점.

 - 쿠키보다 보안에 유리하지만 서버 메모리에 저장되므로 부하를 줄 수 있음

 - 브라우저당 하나의 세션ID가 발급되며 유효시간을 가짐

 - 주로 로그인 상태 유지 기능이나 쇼핑몰의 장바구니 담기 기능등에 사용된다.

 

세션 실행과정

 - 서블릿이 세션 객체를 생성한 후 세션객체에 대한 세션ID를 클라이언트에 응답.

  --> 서버로부터 전송된 세션ID도 쿠키이다.

 - 재접속할 때 세션 쿠키에 저장된 세션ID를 서버에 전송하면 서버에는 해당 ID를 통해 세션 객체에 접근.

 - 즉 서버는 request객체를 통해서 클라우저의 sessionID를 알 수 있다.

 

서블릿에서 세션을 이용할려면 HttpSession 객체를 생성해서 사용한다. 

@WebServlet("/sess")
public class sessionTest extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html; charset=utf-8");
		PrintWriter out = response.getWriter();
		
		// 최초 요청시 세션 객체를 새로생성. 기존에 있었다면 반환.
		HttpSession session = request.getSession();
		if(session.isNew())
		{
			System.out.println("새로운 세션 생성");
		}
		
		out.println(" 세션 아이디 : " + session.getId() + "<br>");
		out.println(" 최초 세션 생성 시각 : " + new Date(session.getLastAccessedTime()) +"<br>");
		out.println(" 세션 유효시간 : " + session.getMaxInactiveInterval() + "<br>");
	}

}

 

세션 유효시간은 따로 설정하지 않으면 기본 30분.

 

이제 세션을 이용한 간단한 로그인을 구현해보자.

html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>회원 가입 창</title>
</head>
<body>
	<form name="frmLogin" method="post" action="login" enctype="UTF-8">
	아이디 : <input type="text" name="user_id"><br>
	비밀번호 : <input tpye="password" name="user_pwd"><br>
	<input type="submit" value="로그인"/>
	<input type="reset" value="초기화" />
	</form>
</body>
</html>

 

로그인 서블릿

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doHandle(request,response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doHandle(request,response);
	}
	
	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException
	{
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		String user_id = request.getParameter("user_id");
		String user_pwd = request.getParameter("user_pwd");
		
		MemberVO vo = new MemberVO();
		vo.id = user_id;
		vo.pwd = user_pwd;
		
		MemberDAO dao = new MemberDAO();
		boolean result = dao.isExisted(vo);
		
		// 로그인정보가 있다면
		if(result)
		{
			HttpSession session = request.getSession();
			session.setAttribute("IsLogon", true);
			session.setAttribute("login.id", user_id);
			session.setAttribute("login.pwd", user_pwd);	
			
			out.println("<html><body>");
			out.println("안녕하세요 " + user_id + " 님!");
			out.println("<a href='show'> 회원정보보기 </a>");
			out.print("</body></html>");
		}
		else
		{
			out.println("<html><body>");
			out.println("로그인 정보가 없음.");
			out.println("<a href='memberForm.html'> 다시로그인 </a>");
			out.print("</body></html>");
		}
			
	}

}

주의할점은 같은 웹브라우저에서 여러 아이디/비밀번호로 로그인시 같은 세션으로 취급이 된다는점.

 

 

show 서블릿

@WebServlet("/show")
public class ShowMember extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		String id = "", pwd ="";
		Boolean isLogon = false;
		HttpSession session = request.getSession(false);
		
		if(session != null)
		{
			isLogon = (Boolean)session.getAttribute("isLogon");
			if(isLogon)
			{
				id = (String)session.getAttribute("login.id");
				pwd = (String)session.getAttribute("login.pwd");
				
				out.print("<html><body>");
				out.print("세션을 활용한 로그인 성공! id : " + id + " PWD : " + pwd);
				out.print("</body></html>");
			}
			else
			{
				response.sendRedirect("memberForm.html");	
			}
		}
		else
		{
			response.sendRedirect("memberForm.html");
		}
		
	}
}

로그인 실패시

로그인 성공시

회원정보보기

 

 

이렇게 서블릿들의 스코프(접근 범위)를 정리해보면

스코프 종류 해당 서블릿 api 속성의 스코프
애플리케이션 스코프 ServletContext 속성은 애플리케이션 전체서 접근가능
세션 스코프 HttpSession 속성은 브라우저(사용자)에서만 접근
리퀘스트 스코프 HttpServletRequest 속성은 해당 요청사이클ㅇ에서만 접근
dispatch등으로 다른 서블릿/jsp에 전달은 가능.

 

서블릿의 여러가지 URL 패턴

URL이란 리소스의 위치(Locator)를 의미.

URL 패턴이란 실제 서블릿의 매핑 이름을 말한다. 클라이언트가 브라우저에 요청할 때 사용되며 반드시 /(슬래시)로 시작해야한다.

 

종류 1 : 정확히 이름까지 일치하는지

@WebServlet("/first/test")
public class LoginServlet extends HttpServlet {

종류 2 : 디렉터리 이름만 일치하는 URL 패턴

@WebServlet("/first/*")
public class LoginServlet extends HttpServlet {

종류 3 : 매핑 이름에 상관없이 확장자가 일치하면 실행.

@WebServlet("*.do")
public class LoginServlet extends HttpServlet {

 

Filter API

필터는 브라우저에서 서블릿에 요청하거나 응답할 때 미리요청과 응답과 관련해 여러가지 작업을 처리하는 기능.

 -> 서블릿에서 반복적으로 처리하는 작업이 있을 때(공통작업) 미리 필터에서 처리.

 

항상 이때까지 작업을 할 때

private void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException
{
	request.setCharacterEncoding("utf-8");
	response.setContentType("text/html;charset=utf-8");

위와 같이 한글 인코딩 설정을 서블릿마다 상단에 추가해주어야 했음.

하지만 모든 서블릿에서 공통으로 처리하는 작업을 먼저 필터에서 추가해준다면 매번 서블릿에 추가하지않더라도 작업이 가능해진다.

 

필터의 종류

 - 요청 필터 : 사용자 인증 및 권한 검사, 요청시 요청 관련 로그 작업,인코딩 가능

 - 응답 필터 : 응답 결과에 대한 암호화 작업, 서비스 시간 츶겅

 

필터는 Filter 인터페이스를 구현한 사용자 정의 클래스가 필요하며, init,doFilter,destory의 메소드를 구현해야한다.

또한 해당 필터를 적용할 Pattern또한 정의해주어야함. 여기서는 모든 필터에 대해 수행하도록 /*로 구현한다.

@WebFilter("/*")
public class EncoderFilter extends HttpFilter implements Filter {
      private ServletContext context;
    /**
     * @see HttpFilter#HttpFilter()
     */
    public EncoderFilter() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public void init(FilterConfig fConfig) throws ServletException {
		context = fConfig.getServletContext();
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// 모든 요청에 대한 인코딩 설정
		request.setCharacterEncoding("utf-8");
		chain.doFilter(request, response);
	}
}

응답에 의한 필터도 위에서 처리가 가능하다.

여러가지 서블릿 관련 Listener API

 

위와 같이 특정 이벤트가 발생했을 때 우리가 호출하는게 아닌, 컨테이너 내에서 서블릿과 관련된 Listener를 상속받은 클래스의 인스턴스의 메소드를 직접 호출해준다.

 -> 만약 그러지않았다면, 멀티 쓰레드 환경의 톰캣 컨테이너에서 사용자가 실수로 경쟁상태를 방관하는 코드를 짠다면 대참사가 난다..

 

예로들어 HttpSessionBindingListener를 구현한 밑의 클래스를 보면

@WebListener
public class LoginImpl implements HttpSessionBindingListener {
	private String user_id;
	private String user_pwd;
	private static int total_user = 0;
	/**
     * @see HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
     */
	
	public LoginImpl(String id, String pwd)
	{
		user_id = id;
		user_pwd = pwd;
	}
	
	@Override
    public void valueBound(HttpSessionBindingEvent event)  { 
    	++total_user;
    }

	/**
     * @see HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
     */
	@Override
    public void valueUnbound(HttpSessionBindingEvent event)  { 
		--total_user;
    }
	
}

위 객체가 생성되고, 세션(HttpSession) 객체의 setAttribute() 메서드를 통해 바인딩되거나 removeAttribute() 메서드를 통해 언바인딩될 때에 대한 이벤트를 처리할 수 있도록 하는 리스너. 

즉 밑과같이

String user_id = request.getParameter("user_id");
String user_pwd = request.getParameter("user_pwd");

// 리스너 객체 생성.
LoginImpl loginuser = new LoginImpl(user_id, user_pwd);
HttpSession session = request.getSession();
if(session.isNew())
{
	// 세션에 리스너 객체 바인딩. setAttribute로 바인딩되었으므로 valueBound가 호출된다.
	session.setAttribute("loginUser", loginuser);
}

 

 

반대로 session객체의 invalidate메소드를 호출하면 리스너 객체의 valueUnbound가 호출된다.

 

 

반응형

'웹프로그래밍 > JSP_Servlet' 카테고리의 다른 글

JSP - 2  (0) 2023.08.11
JSP  (0) 2023.08.11
서블릿(Servlet) - 2  (0) 2023.08.07
서블릿(Servlet) 기초  (0) 2023.08.07
웹 어플리케이션의 기본구조 및 학습  (0) 2023.08.07
Comments