Programmer's Progress

세션을 활용하여 로그인 정보 유지하기 본문

Servlet + JSP/Self Learning

세션을 활용하여 로그인 정보 유지하기

Blanc et Noir 2021. 7. 4. 19:54

  사람들은 로그인 페이지에서 한번 로그인을 하면, 그 서버의 모든 웹페이지가 나의 로그인 정보를 알고 있을 것이라

생각하기도 한다. 그러나 실상은 그렇지 않다.

 

  HTTP 프로토콜은 애플리케이션 레이어상에서 쓰이며 stateless한 프로토콜이다. 서버에서 클라이언트의 정보를 저장해두지 않는다. HTTP Request 메세지에는 요청 시에 필요한 정보가 GET, POST 등의 방식으로 전달이 된다.

즉, 그 메세지에는 요청에 필요한 모든 정보가 이미 들어있기에, 굳이 서버에서 정보를 추가적으로 저장할 필요가 없다.

 

  트랜스포트 레이어상에서, 특히 TCPReliable Data Transfer를 제공하기 위해서 Go-Back-N 방식과

Selective Repeat방식으로 이를 처리하고, RDT시간을 줄이기 위해서 delay-based 혹은 loss-based 등의 방식으로

데이터 전달속도를 조절하여 혼잡 제어를 하기도 한다. 혼잡 제어를 위해서는 클라이언트의 윈도 사이즈도

알아야 할 필요가 있을 텐데, 이런 정보를 서버에 저장하지 않는다는 것이다. 그 윈도 사이즈마저도 HTTP Request

헤더 부분에 존재하기 때문이다.

 

 

  이처럼 HTTP 프로토콜은 무상태 프로토콜로써 동작하는데도 도대체 어떻게 로그인 정보를 유지할 수 있는 것일까?

비법은 쿠키와 세션에서 찾을 수 있다.

 

  데이터 통신 과목에서 배웠었던 개념인 쿠키와 세션에 관하여 말하자면

쿠키는 클라이언트 사이드에 파일의 형태로 존재하며, 처음 애플리케이션을 요청할 때에는 기본 HTTP Request

전달하지만, 서버에서 쿠키를 만들어 HTTP Response에 담아 전달하면, 클라이언트는 이 쿠키파일을 다운로드하여

다음에 서버에 다시 HTTP Request를 전달할때 쿠키를 첨부하여 전달하는 방식이다.

 

  하지만 클라이언트 사이드의 파일의 형태로 존재하기때문에

당연히 보안에 취약할 수밖에 없고 데이터의 양에도 한계가 있다.

 

  하지만 세션방식은 다르다.

클라이언트에서 서버에 애플리케이션을 요청할 때 기본 HTTP Request를 전달하지만, 서버에서 jsessionID를 발급하고

이걸 세션쿠키에 담아서 다시 HTTP Response로 전달하면, 클라이언트는 이 세션 쿠키를 저장하고, 서버도 마찬가지로

Session 객체를 자신의 메모리에 저장한다. 이러한 세션 ID는 브라우저마다 하나씩 발급되며, 일반적인 쿠키의 경우는 도메인 마다 하나씩 발급된다. 

 

  클라이언트가 다시 애플리케이션을 요청할때엔 세션 쿠키를 같이 전달하고, 이 세션 쿠키에 저장된 세션 ID를 가지고

세션 객체를 찾아, 클라이언트의 정보를 얻어낼 수 있는 것이다. 즉 로그인 유지를 할 수 있게 된다.

 

MainServlet

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
	name="MainServlet",
	urlPatterns = {"/main"}
)
public class MainServlet extends HttpServlet{
	ServletConfig config;
	ServletContext context;
	String col1, col2;
	@Override
	public void init(ServletConfig config) throws ServletException{
		System.out.println("MainServlet init( )");
		super.init(config);
		this.config = config;
		this.context = config.getServletContext();
		col1 = this.context.getInitParameter("col1");
		col2 = this.context.getInitParameter("col2");
	}
	@Override
	public void destroy(){
		System.out.println("MainServlet destroy( )");
	}
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("MainServlet doGet( )");
		doHandle(request,response);
	}
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("MainServlet doPost( )");
		doHandle(request,response);
	}

	public void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("MainServlet doHandle( )");
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.print("<html>");
		out.print("<form action='login' method='post'>");
		out.print("<input type='text' placeholder='아이디' name='"+col1+"' autocomplete='off'>");
		out.print("<input type='password' placeholder='비밀번호' name='"+col2+"' autocomplete='off'>");
		out.print("<input type='submit' value='로그인'>");
		out.print("</form>");
		out.print("</html>");
		out.close();
	}
}

 

LoginServlet

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet(
	name="LoginServlet",
	urlPatterns = {"/login"}
)
public class LoginServlet extends HttpServlet{
	ServletConfig config;
	ServletContext context;
	String col1, col2;
	@Override
	public void init(ServletConfig config) throws ServletException{
		System.out.println("LoginServlet init( )");
		super.init(config);
		this.config = config;
		this.context = config.getServletContext();
		col1 = this.context.getInitParameter("col1");
		col2 = this.context.getInitParameter("col2");
	}
	@Override
	public void destroy(){
		System.out.println("LoginServlet destroy( )");
	}
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LoginServlet doGet( )");
		doHandle(request,response);
	}
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LoginServlet doPost( )");
		doHandle(request,response);
	}

	public void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LoginServlet doHandle( )");
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		UserDAO dao = new UserDAO();
		ArrayList<UserVO> list = dao.getList(col1, col2);
		String id = request.getParameter(col1), pw = request.getParameter(col2);
		boolean flag = false;
		
		for(int i=0; i<list.size(); i++) {
			if(list.get(i).getUser_id().equals(id)&&list.get(i).getUser_pw().equals(pw)) {
				flag = true;
				break;
			}
		}
		HttpSession session = request.getSession();
		if(session.isNew()) {
			if(flag == true) {
				System.out.println("Login request has been accepted");
				session.setAttribute(col1, id);
				session.setAttribute(col2, pw);
				System.out.println("Session has been constructed");
			}else {
				System.out.println("Login request has not been accepted");
				session.invalidate();
				System.out.println("Session has been closed");
			}
		}else {
			if(flag == true) {
				System.out.println("Login request has been accepted");
			}else {
				System.out.println("Login request has not been accepted");
				session.invalidate();
				System.out.println("Session has been closed");
			}
		}
		response.sendRedirect("view");
		out.close();
	}
}

 

LogoutServlet

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet(
	name="LogoutServlet",
	urlPatterns = {"/logout"}
)
public class LogoutServlet extends HttpServlet{
	ServletConfig config;
	ServletContext context;
	String col1, col2;
	@Override
	public void init(ServletConfig config) throws ServletException{
		System.out.println("LogoutServlet init( )");
		super.init(config);
		this.config = config;
		this.context = config.getServletContext();
		col1 = this.context.getInitParameter("col1");
		col2 = this.context.getInitParameter("col2");
	}
	@Override
	public void destroy(){
		System.out.println("LogoutServlet destroy( )");
	}
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LogoutServlet doGet( )");
		doHandle(request,response);
	}
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LogoutServlet doPost( )");
		doHandle(request,response);
	}

	public void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("LogoutServlet doHandle( )");
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		HttpSession session = request.getSession();
		String id = (String) session.getAttribute(col1), pw = (String) session.getAttribute(col2);
		
		session.invalidate();
		System.out.println("Session has been closed");
		out.print("<html>");
		out.print("<p>로그아웃 완료</p>");
		out.print("<a href='main'>다시 로그인</a>");
		out.print("</html>");
		out.close();
	}
}

 

ViewServlet

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet(
	name="ViewServlet",
	urlPatterns = {"/view"}
)
public class ViewServlet extends HttpServlet{
	ServletConfig config;
	ServletContext context;
	String col1, col2;
	@Override
	public void init(ServletConfig config) throws ServletException{
		System.out.println("ViewServlet init( )");
		super.init(config);
		this.config = config;
		this.context = config.getServletContext();
		col1 = this.context.getInitParameter("col1");
		col2 = this.context.getInitParameter("col2");
	}
	@Override
	public void destroy(){
		System.out.println("ViewServlet destroy( )");
	}
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("ViewServlet doGet( )");
		doHandle(request,response);
	}
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("ViewServlet doPost( )");
		doHandle(request,response);
	}

	public void doHandle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
		System.out.println("ViewServlet doHandle( )");
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		HttpSession session = request.getSession();
		String id = (String) session.getAttribute(col1), pw = (String) session.getAttribute(col2);
		
		out.print("<html>");
		if(id!=null&&pw!=null) {
			out.print("<p>아이디 : "+id+"</p>");
			out.print("<p>비밀번호 : "+pw+"</p>");
			out.print("<a href='logout'>로그아웃</a>");
		}else {
			out.print("<p>로그인 정보가 유효하지 않습니다</p>");
			out.print("<a href='main'>로그인</a>");
			session.invalidate();
			System.out.println("Session has been closed");
		}		
		out.print("</html>");
		out.close();
	}
}

 

서블릿은 각각 메인화면, 로그인 화면, 로그아웃 화면, 상태 정보화면으로 구성했다.

JSP를 알고있다면, 일일이 out.print( )를 사용하지 않아도 되겠지만, 지금은 모르는 상태이므로

일단 간단하게 나마 화면을 구성했다.

 

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
	<context-param>
		<param-name>col1</param-name>
		<param-value>user_id</param-value>
	</context-param>
	<context-param>
		<param-name>col2</param-name>
		<param-value>user_pw</param-value>
	</context-param>
</web-app>

위의 코드는, 컨텍스트 파라미터로 col1, col2를 각각 user_iduser_pw로 설정한 것이다.

이렇게 컨텍스트 파라미터로 지정하면, DB의 칼럼명이 바뀌어도 쉽게 수정이 가능해지며

모든 서블릿들이 해당 데이터를 공유할 수 있다는 점에서 유지보수성이 높아진다.

 

아이디 qwe789, 비밀번호 789로 로그인을 시도하면 LoginServlet이 수행된다. 첫 로그인이므로 session.isNew()는 true이다.

 

로그인후에 로그아웃을 시도하면 세션이 종료된다.

 

로그아웃없이 다시 로그인을 시도하면 request.getSession( )에서 반환되는 세션은 기존에 존재하는 세션이다. 따라서 로그인 정보가 유지된다.

 

로그아웃된 상태에서 바로 로그인 정보를 확인하려 하면 로그인을 요구한다. 즉, 이전에 클라이언트가 로그아웃 했음을 알고있는 것이다.

 

로그인을 한 상태로 메인페이지로 갔다가, 로그인 정보를 확인해보면 정상적으로 로그인 정보가 출력된다. 로그인 정보가 성공적으로 유지된다.

 

처음에는 바로 로그인 정보를 확인하고, 로그인 요구에 따라 로그인을 시도하면

로그인이 정상적으로 되지 않는 문제가 있었다.

 

확인해보니 바로 view 서블릿을 요청했을때, 로그인을 요구하는 것까지는 제대로 동작했는데

session.invalidate( )메소드를 호출하지 않아서 세션이 유지가 되는 상황이 나타났다.

 

앞으로는 세션 종료하는 시점을 잘 고려해서 설계해야겠다.

Comments