관리 메뉴

거니의 velog

(23) 쿠키와 세션 알아보기 3 본문

Java_Servlet

(23) 쿠키와 세션 알아보기 3

Unlimited00 2023. 8. 29. 20:00

4. 세션을 이용한 웹 페이지 연동 기능

* 이번에는 세션을 이용해 웹 애플리케이션을 연동하는 방법을 알아보자.

* 세션 역시 웹 페이지들 사이의 공유 정보를 서버에 저장해 두고 웹 페이지들을 매개해 주는 방법이라는 점에서는 쿠키와 같다. 하지만 쿠키는 사용 시 웹 페이지들의 정보가 클라이언트 PC에 저장되므로 정보가 쉽게 노출될 수 있다는 단점이 있는 반면, 세션은 서버의 메모리에 생성되어 정보를 저장한다. 따라서 웹 페이지에서 사용되는 정보 중에 로그인 정보처럼 보안이 요구되는 정보는 대부분 세션을 이용한다.

* 세션은 각 브라우저당 한 개, 즉 사용자당 한 개가 생성된다. 사용자의 로그인 상태가 쇼핑몰의 장바구니 담기 기능 같은 정보를 해당 브라우저의 세션에 저장해 두고 사용하면 편리하다.

* 세션의 특징은 다음과 같다.

- 정보가 서버의 메모리에 저장된다.

- 브라우저의 세션 연동은 세션 쿠키를 이용한다.

- 쿠키보다 보안에 유리하다.

- 서버에 부하를 줄 수 있다.

- 브라우저(사용자)당 한 개의 세션(세션 id)이 생성된다.

- 세션은 유효 시간을 가진다(기본 유효 시간은 30분이다).

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

(1) 세션 기능 실행 과정

* 클라이언트의 브라우저가 서버에 최초 접속하면 서버의 서블릿은 세션 객체를 생성한 후 세션 객체에 대한 세션 id를 브라우저에 전송한다. 그러면 브라우저는 이 세션 id를 브라우저가 사용하는 세션 쿠키에 저장한다. 즉, 서버로부터 전송된 세션 id도 쿠키이며, 쿠키 이름은 jsessionId 이다.

* 그리고 재접속하여 세션 쿠키에 저장된 세션 id(jsessionId)를 다시 서버로 전송하면 서버에서는 전송된 세션 id를 이용해 브라우저의 세션 객체에 접근하여 브라우저에 대한 작업을 수행한다.

(1) 브라우저로 사이트에 접속한다.

(2) 서버는 접속한 브라우저에 대한 세션 객체를 생성한다.

(3) 서버는 생성된 세션 id를 클라이언트 브라우저에 응답한다.

(4) 브라우저는 서버로부터 받은 세션 id를 브라우저가 사용하는 메모리의 세션 쿠키에 저장한다(쿠키 이름은 jsessionId).

(5) 브라우저가 재접속하면 브라우저는 세션 쿠키에 저장된 세션 id를 서버에 전달한다.

(6) 서버는 전송된 세션 id를 이용해 해당 세션에 접근하여 작업을 수행한다.

* 세션의 중요한 특징은 브라우저당 한 개씩 생성된다는 점이다. 그러므로 브라우저가 서버에 접속하여 브라우저에 저장된 세션 id(jsessionId)를 전송하면 서버는 그 값을 이용해서 해당 브라우저에 대한 세션을 구분하고 각 브라우저에 대한 세션 작업을 수행한다.


(2) 세션 API의 특징과 기능

* 서블릿에서 세션을 이용하려면 HttpSession 클래스 객체를 생성해서 사용해야 한다. HttpSession 객체는 HttpServletRequest의 getSession() 메서드를 호출해서 생성한다.

* 세션을 얻는 getSession() 메서드로는 다음과 같은 것들이 있다.

- getSession() : 기존의 세션 객체가 존재하면 반환하고, 없으면 새로 생성한다.

- getSession(true) : 기존의 세션 객체가 존재하면 반환하고, 없으면 새로 생성한다.

- getSession(false) : 기존의 세션 객체가 존재하면 반환하고, 없으면 null을 반환한다.

<HttpSession 클래스의 여러 가지 메서드>

반환 타입 메서드 설명
Object getAttribute(String name) 속성 이름이 name인 속성 값을 Object 타입으로 반환한다. 해당되는 속성 이름이 없을 경우 null 값을 반환한다.
Enumeration getAttributeNames() 세션 속성 이름들을 Enumeration 객체 타입으로 반환한다.
long getCreationTime() 1970년 1월 1일 0시 0초를 기준으로 현재 세션이 생성된 시간까지 경과한 시간을 계산하여 1/1000초 값으로 반환한다.
String getId() 세션에 할당된 고유 식별자를 String 타입으로 반환한다.
int getMaxInactiveInterval() 현재 생성된 세션을 유지하기 위해 설정된 세션 유지 시간을 int 타입으로 반환한다.
void invalidate() 현재 생성된 세션을 소멸한다.
boolean isNew() 최초로 생성된 세션인지 기존에 생성되어 있었던 세션인지 판별한다.
void removeAttribute(String name) 세션 속성 이름이 name인 속성을 제거한다.
void setAttribute
(String name, Object value)
세션 속성 이름이 name인 속성에 속성 값으로 value를 할당한다.
void setMaxInactiveInterval(int interval) 세션을 유지하기 위한 세션 유지 시간을 초 단위로 설정한다.

(3) 서블릿에서 세션 API 이용하기

* 그럼 지금부터 세션 API를 이용해 직접 세션을 생성해 보자.

1. 다음과 같이 세션 테스트를 위한 실습 파일인 SessionTest 클래스를 준비한다.

2. SessionTest 클래스를 다음과 같이 작성한다. request의 인자 없는 getSession() 메서드를 호출하여 세션이 없으면 새로 생성하고, 세션이 있으면 기존 세션을 가져온다. 또한 세션 객체의 getMaxInactiveInterval()를 호출하여 생성된 세션의 유효 시간을 가져온다.

package sec03.ex01;

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

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("/sess")
public class SessionTest extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession(); // getSession() 을 호출하여 최초 요청 시 세션 객체를 새로 생성하거나 기존 세션을 반환한다.
		out.println("세션 아이디 : " + session.getId() + "<br>"); // 생성된 세션 객체의 id를 가져온다.
		out.println("최초 세션 생성 시각 : " + new Date(session.getCreationTime()) + "<br>"); // 최초 세션 객체 생성 시간을 가져온다.
		out.println("최근 세션 접근 시각 : " + new Date(session.getLastAccessedTime()) + "<br>"); // 세션 객체에 가장 최근에 접근한 시간을 가져온다.
		out.println("세션 유효 시간 : " + session.getMaxInactiveInterval() + "<br>"); // 세션 객체의 유효 시간을 가져온다.
		if (session.isNew()) {
			out.print("새 세션이 만들어졌습니다.");
		} // 최초 생성된 세션인지 판별한다.
	}

}

3. 브라우저에서 최초 요청 시 생성된 세션 객체에 할당된 세션 id와 여러 가지 정보를 출력한다. 최초 생성된 세션이므로 "새 세션이 만들어졌습니다." 라는 메시지가 출력된다.

- http://localhost:8090/pro09/sess

4. 같은 브라우저에서 다른 탭을 열고 요청하면 같은 세션 id를 출력하므로 최초 생성된 세션을 재사용한다. 따라서 "새 세션이 만들어졌습니다." 라는 메시지는 출력되지 않는다.

* 다음은 서블릿에서 생성된 세션 id, 즉 브라우저로 전송되어 세션 쿠키에 쿠키 이름 jsessionID로 저장된 세션 id이다.

* 현재 윈도우 10 운영체제에서는 같은 PC의 브라우저나 다른 탭에서 요청을 해도 기존 세션을 유지한다. 그러면 이번에는 같은 PC에서 기존 브라우저의 세션으로 연결되지 않고, 다른 브라우저로 다른 세션을 만드는 방법을 알아보자. 즉, 새 세션으로 브라우저를 여는 과정이다.


(4) 다른 브라우저에서 새 세션 만들기

* 브라우저에서 Ctrl + Shift + N을 눌러 시크릿 모드의 크롬을 실행한다.

* 주소창에서 /sess로 요청하면 새로운 세션을 생성한 후 다른 세션 id를 출력한다.

* 세션 기본 유효 시간은 톰캣 컨테이너에서 지정한 30분(1800초)이다. 그러나 HttpSession의 setMaxInactiveInterval() 메서드를 이용하면 사용자가 원하는 세션 유효 시간을 설정할 수 있다. 그리고 invalidate() 메서드를 이용하면 세션을 언제든지 삭제할 수도 있다.

* 다음과 같이 톰캣 컨테이너의 web.xml에 세션 기본 유효 시간이 설정된 것을 확인할 수 있다.

* 이번에는 제공되는 메서드들을 이용해서 직접 세션 유효 시간을 재설정해 보자.

1. 다음과 같이 SessionTest2 클래스를 준비한다.

2. SessionTest2 클래스를 다음과 같이 작성한다. setMaxInactiveInterval() 메서드를 이용해 세션 유효 시간을 5초로 재설정한다.

package sec03.ex02;

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

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("/sess2")
public class SessionTest2 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		out.println("세션 아이디 : " + session.getId() + "<br>");
		out.println("최초 세션 생성 시각 : " + new Date(session.getCreationTime()) + "<br>");
		out.println("최근 세션 접근 시각 : " + new Date(session.getLastAccessedTime()) + "<br>");
		
		out.println("기본 세션 유효 시간 : " + session.getMaxInactiveInterval() + "<br>"); // 톰캣의 기본 세션 유효 시간을 출력한다.
		session.setMaxInactiveInterval(5); // 세션의 유효 시간을 5초로 설정한다.
		out.println("세션 유효 시간 : " + session.getMaxInactiveInterval() + "<br>"); // 유효 시간을 재설정한 후 세션 유효 시간을 출력한다.
		
		if (session.isNew()) {
			out.print("새 세션이 만들어졌습니다.");
		}
	}

}

3. 최초에 /sess2로 요청하여 설정 전 유효 시간과 설정 후 유효 시간을 출력한다.

4. 5초가 지난 후 같은 브라우저에서 재요청하면 다시 새 세션이 생성된다.

* 최초 요청 시 세션의 id와 5초 경과 후 요청 시 세션의 id가 다르다. 즉, 5초 이후에는 기존 세션은 삭제되고 재요청 시 새 세션이 생성된다.

* 세션 유효 시간을 재설정하는 경우는 주로 은행 사이트에서 자주 발생한다. 우리가 보통 은행 사이트에 로그인하면 화면 위쪽에 10분에서 초 단위로 역카운팅이 되는 것이 보인다. 만약 10분 이상 아무 작업도 하지 않으면 로그인 상태를 기억하는 세션이 자동 삭제되어 자동 로그아웃 화면을 출력한다.

* 이번에는 사용자가 강제로 세션을 삭제하는 기능을 실습해 보자.

1. 다음과 같이 실습 파일을 준비한다.

2. SessionTest3 클래스를 다음과 같이 작성한다. invalidate() 메서드를 이용해 강제로 세션을 삭제한다.

package sec03.ex03;

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

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("/sess3")
public class SessionTest3 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		out.println("세션 아이디: " + session.getId() + "<br>");
		out.println("최초 세션 생성 시각: " + new Date(session.getCreationTime()) + "<br>");
		out.println("최근 세션 접근 시각 : " + new Date(session.getLastAccessedTime()) + "<br>");
		out.println("세션 유효 시간 : " + session.getMaxInactiveInterval() + "<br>");
		if (session.isNew()) {
			out.print("새 세션이 만들어졌습니다.");
		}
		session.invalidate(); // invalidate()를 호출하여 생성된 세션 객체를 강제로 삭제한다.

	}

}

3. 최초 요청 시 새 세션이 생성된 후 invalidate() 메서드가 호출되므로 바로 소멸된다.

- http://localhost:8090/pro09/sess3

4. 재요청 시 다른 세션이 생성된다.


(5) 세션을 이용한 로그인 정보 바인딩 실습

* 지금까지는 HttpServletContext와 HttpServletRequest의 바인딩 기능을 사용했다. 그런데 로그인 상태처럼 사용자와 관련된 정보를 바인딩해서 사용할 때는 세션을 이용하는 것이 편리하다. 세션은 사용자당 한 개씩 생성되기 때문이다. 이번에는 로그인을 이용해서 HttpSession의 바인딩 기능을 알아보자.

1. 실습하기 전에 해야 할 일이 있다. 톰캣이 종료된 후에도 세션이 메모리에 삭제되지 않는 경우가 있으므로 톰캣 설정 파일인 context.xml을 열어 <Managet pathname="" /> 태그의 주석을 해제해야 한다.

2. 다음과 같이 실습 파일을 준비한다.

3. 로그인창에서 ID와 비밀번호를 입력한 후 서블릿으로 전송할 수 있도록 login2.html 파일을 작성한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인창</title>
    </head>

    <body>
        <form name="frmLogin" method="post" action="login2" enctype="UTF-8">
            아이디 :<input type="text" name="user_id"><br>
            비밀번호:<input type="password" name="user_pw"><br>
            <input type="submit" value="로그인">
            <input type="reset" value="다시입력">
        </form>
    </body>
</html>

4. SessionTest4 클래스를 다음과 같이 작성한다. 로그인창에서 로그인한 경우 ID와 비밀번호를 가져오고, 최초 요청 시 세션에 setAttribute() 메서드를 이용해 user_id로 사용자 ID를 바인딩하도록 구현한다. <a> 태그를 이용해 재요청하고 세션의 getAttribute() 메서드를 이용하여 user_id 값을 가져와 로그인 여부를 확인한다.

package sec03.ex04;

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

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("/login2")
public class SessionTest4 extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw"); // 로그인창에서 전송된 ID와 비밀번호를 가져온다.
		if (session.isNew()){ // 최초 요청 시 수행한다.
			if(user_id != null){
				session.setAttribute("user_id", user_id);
				out.println("<a href='login2.html'>로그인 상태 확인</a>");
			}else {
				out.print("<a href='login2.html'>다시 로그인 하세요!!</a>");
				session.invalidate();
			} // 로그인창에서 서블릿으로 요청했다면 ID가 null이 아니므로 세션에 ID를 바인딩한다.
		}else{
			user_id = (String) session.getAttribute("user_id");
			if (user_id != null && user_id.length() != 0) {
				out.print("안녕하세요 " + user_id + "님!!!");
			} else {
				out.print("<a href='login2.html'>다시 로그인 하세요!!</a>");
				session.invalidate();
			}
		} // 재요청시 세션에서 ID를 가져와 이전에 로그인 했는지 여부를 확인한다.
	}

}

5. 로그인창 요청 후 ID와 비밀번호를 입력하고 전송한다.

- http://localhost:8090/pro09/login2.html

6. 최초 로그인 시 세션에 ID를 바인딩 한다.

7. 다시 로그인 상태 확인을 클릭해 /login2로 재요청하면 현재 로그인 상태를 출력한다.

8. 톰캣 재실행 후 로그인창을 거치지 않고 바로 /login2로 요청하면 세션에 ID가 없으므로 "다시 로그인 하세요!!"라는 메시지가 출력된다.

- http://localhost:8090/pro09/login2

* 이상으로 세션 기능을 알아보았다. 세션은 브라우저 한 개당 생성되므로 로그인 상태 정보나 쇼핑몰 장바구니 정보 등 브라우저에만 사용하는 정보를 바인딩해서 사용하면 편리하다는 것을 잊지 말자!


5. encodeURL() 사용법

* 앞에서는 쿠키와 세션 기능을 알아봤다. 세션 역시 클라이언트의 세션 쿠키를 이용해 각 브라우저에 대한 세션 기능을 사용한다. 그런데 만약 브라우저에서 쿠키 기능을 사용할 수 없게 설정했다면 쿠키 기능은 물론 세션 기능도 사용할 수 없다. 이럴 때는 encodeURL() 메서드를 이용해 직접 서버에서 브라우저로 응답을 먼저 보낸 후 URL Rewriting 방법을 이용해 jsessionID를 서버로 전송하여 세션 기능을 사용하면 된다.


(1) 브라우저에서 쿠키 사용 금지하기

* 이번에는 크롬 브라우저에서 쿠키 사용을 금지하는 방법을 알아보자.

1. 크롬 브라우저를 실행하고 오른쪽 상단에서 더 보기 아이콘 클릭 후 설정을 클릭한다.

2. '개인정보 보호 및 보안'에서 콘텐츠 설정을 클릭한다.

3. 쿠키를 클릭해 '사이트에서 쿠키 데이터 저장 및 읽기 허용'을 차단하도록 하자.

4. '차단됨' 옆의 옵션을 클릭하여 차단으로 설정한다.


(2) encodeURL() 메서드를 이용한 세션 사용 실습

* 이번에는 세션 쿠키를 사용하지 않고 encodeURL() 메서드를 이용해 jsessionId의 세션 id를 브라우저에 응답으로 전송한 후 세션을 사용해 보자.

1. 다음과 같이 실습 파일을 준비한다.

2. SessionTest5 클래스를 다음과 같이 작성한다. 다시 /login으로 요청해 jsessionId를 URL rewriting 방식으로 가져온 후 세션에 접근하여 로그인 상태 유무를 판단하도록 구현한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인창</title>
    </head>

    <body>
        <!-- login3으로 경로 수정 -->
        <form name="frmLogin" method="post" action="login3" enctype="UTF-8">
            아이디 :<input type="text" name="user_id"><br>
            비밀번호:<input type="password" name="user_pw"><br>
            <input type="submit" value="로그인">
            <input type="reset" value="다시입력">
        </form>
    </body>
</html>
package sec04.ex01;

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

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("/login3")
public class SessionTest5 extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		if (session.isNew()){
			if(user_id != null){
				session.setAttribute("user_id", user_id);
				String url = response.encodeURL("login2"); // 변수 url에 encodeURL()을 이용해 응답 시 미리 jssesionId를 저장한다.
				out.println("<a href="+url+">로그인 상태 확인</a>"); // 로그인 상태 확인 클릭 시 jsessionId를 서블릿으로 다시 전송한다.
			}else {
				out.print("<a href='login2.html'>다시 로그인 하세요!!</a>");
				session.invalidate();
			}
		}else{
			user_id = (String) session.getAttribute("user_id");
			if (user_id != null && user_id.length() != 0) {
				out.print("안녕하세요 " + user_id + "님!!!");
			} else {
				out.print("<a href='login2.html'>다시 로그인 하세요!!</a>");
				session.invalidate();
			}
		}
	}

}

3. 로그인창에서 ID와 비밀번호를 입력하고 로그인한다.

- http://localhost:8090/pro09/login2.html

4. 로그인 상태 확인을 클릭한다.

5. 서블릿에 jsessionId 쿠키 값을 전송해 로그인 상태를 유지한다.

* 대부분의 브라우저는 쿠키 사용을 기본으로 설정하고 있지만 쿠키를 사용할 수 없을 경우에는 이렇게 encordURL() 메서드를 이용해 세션 기능을 사용할 수 있다.


6. 세션을 이용한 로그인 예제

* 이번에는 실제 웹 사이트에서 데이터베이스와 연동한 후 세션을 이용해 로그인 상태를 유지하는 예제를 실습해 보자.

1. 먼저 데이터베이스 연동과 관련된 설정을 해준다. 이전에 실습한 회원기능 실습 자바 클래스 파일인 MemberDAO와 MemverVO를 복사하여 붙여 넣는다.

2. 사용자의 ID와 비밀번호를 입력한 후 /login4 서블릿으로 전송하도록 login3.html을 작성한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인창</title>
    </head>

    <body>
        <form name="frmLogin" method="post" action="login4" enctype="UTF-8">
            아이디 :<input type="text" name="user_id"><br>
            비밀번호:<input type="password" name="user_pwd"><br>
            <input type="submit" value="로그인">
            <input type="reset" value="다시입력">
        </form>
    </body>
</html>

3. 로그인창의 요청을 처리하는 LoginServlet 클래스를 다음과 같이 작성한다. 로그인창에서 전송된 ID와 비밀번호를 가져와 MemberVO 객체를 생성한 후 속성에 ID와 비밀번호를 설정한다. 그런 다음 MemberDAO의 isExisted() 메서드를 호출하면서 memberVO를 인자로 전달한다. 조회된 결과가 true이면 isLogOn 속성을 true로 세션에 저장하고, ID와 비밀번호도 세션에 저장한다.

package sec05.ex01;

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

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("/login4")
public class LoginServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request, HttpServletResponse response)	throws ServletException, 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"); // 로그인창에서 전송된 ID와 비밀번호를 가져온다.

		MemberVO memberVO = new MemberVO();
		memberVO.setId(user_id);
		memberVO.setPwd(user_pwd); // MemberVO 객체를 생성하고 속성에 ID와 비밀번호를 설정한다.
		
		MemberDAO dao = new MemberDAO();
		boolean result = dao.isExisted(memberVO); // MemberDAO의 isExisted() 메서드를 호출하면서 memberVO를 전달한다.

		if (result) {
			HttpSession session = request.getSession();
			session.setAttribute("isLogon", true); // 조회한 결과가 true이면 isLogOn 속성을 true로 세션에 저장한다.
			session.setAttribute("login.id", user_id);
			session.setAttribute("login.pwd", user_pwd); // 조회한 결과가 true이면 ID와 비밀번호를 세션에 저장한다.

			out.print("<html><body>");
			out.print("안녕하세요 " + user_id + "님!!!<br />");
			out.print("<a href='show'>회원정보 보기</a>");
			out.print("</body></html>");
		} else {
			out.print("<html><body><div>회원 아이디가 틀립니다.</div>");
			out.print("<a href='login3.html'>다시 로그인하기</a>");
			out.print("</body></html>");
		}
	}

}

4. MemberDAO 클래스를 다음과 같이 작성한다. 오라클에서 제공하는 decode() 함수를 이용해 SQL문으로 회원 정보를 조회한다. 정보가 존재하면 true를, 존재하지 않으면  false를 반환한다.

package sec05.ex01;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MemberDAO {
	private Connection con;
	private PreparedStatement pstmt;
	private DataSource dataFactory;

	public MemberDAO() {
		try {
			Context ctx = new InitialContext();
			Context envContext = (Context) ctx.lookup("java:/comp/env");
			dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public List listMembers() {
		List list = new ArrayList();
		try {
			// connDB();
			con = dataFactory.getConnection();
			String query = "select * from t_member ";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();
			while (rs.next()) {
				String id = rs.getString("id");
				String pwd = rs.getString("pwd");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date joinDate = rs.getDate("joinDate");
				MemberVO vo = new MemberVO();
				vo.setId(id);
				vo.setPwd(pwd);
				vo.setName(name);
				vo.setEmail(email);
				vo.setJoinDate(joinDate);
				list.add(vo);
			}
			rs.close();
			pstmt.close();
			con.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	public void addMember(MemberVO memberVO) {
		try {
			Connection con = dataFactory.getConnection();
			String id = memberVO.getId();
			String pwd = memberVO.getPwd();
			String name = memberVO.getName();
			String email = memberVO.getEmail();
			String query = "insert into t_member";
			query += " (id,pwd,name,email)";
			query += " values(?,?,?,?)";
			System.out.println("prepareStatememt: " + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.setString(2, pwd);
			pstmt.setString(3, name);
			pstmt.setString(4, email);
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void delMember(String id) {
		try {
			Connection con = dataFactory.getConnection();
			Statement stmt = con.createStatement();
			String query = "delete from t_member" + " where id=?";
			System.out.println("prepareStatememt:" + query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public boolean isExisted(MemberVO memberVO) {
		boolean result = false;
		String id = memberVO.getId();
		String pwd = memberVO.getPwd();
		try {
			con = dataFactory.getConnection();
			String query = "select decode(count(*), 1, 'true', 'false') as result from t_member";
				   query += " where id = ? and pwd = ?"; // 오라클의 decode() 함수를 이용해 조회하여 ID와 비밀번호가 테이블에 존재하면 true를, 존재하지 않으면 false를 조회한다.
			System.out.println(query);
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, id);
			pstmt.setString(2, pwd);
			ResultSet rs = pstmt.executeQuery(); // 메서드로 전달된 ID와 비밀번호를 이용해 SQL문을 작성한 후 데이터베이스에 조회한다.
			rs.next(); // 커서를 첫 번째 레크드로 위치시킨다.
			result = Boolean.parseBoolean(rs.getString("result"));
			System.out.println("result=" + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}

5. ShowMember 클래스를 다음과 같이 작성한다. 두 번째 서블릿은 사용자가 로그인할 때 회원 정보를 표시해 준다. 먼저 로그인 상태를 확인하기 위해 getSession(false) 메서드를 호출하여 세션을 얻은 다음 getAttribute() 메서드에 isLogOn을 인자로 전달해 로그인 상태를 가져온다. isLogOn의 값이 true면 회원 정보를 세션에서 가져와 출력한다. 만약 세션이 존재하지 않거나 isLogOn이 false이면 다시 로그인창으로 이동한다.

package sec05.ex01;

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

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("/show")
public class ShowMember extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        String id = "", pwd = "";
        Boolean isLogon = false;
        HttpSession session = request.getSession(false); // 이미 세션이 존재하면 세션을 반환하고, 없으면 null을 반환한다.		

        if(session != null){ // 먼저 세션이 생성되어 있는지 확인한다.
        	isLogon = (Boolean)session.getAttribute("isLogon"); // isLogon 속성을 가져와 로그인 상태를 확인한다.
            if(isLogon == true) { 
		        id = (String)session.getAttribute("login.id");
		        pwd = (String)session.getAttribute("login.pwd");
	   	        out.print("<html><body>");
		        out.print("아이디 : " + id +"<br>");
		        out.print("비밀번호 : " + pwd +"<br>");
		        out.print("</body></html>"); // isLogon이 true면 로그인 상태이므로 회원 정보를 브라우저에 표시한다.
		    }else{
		       response.sendRedirect("login3.html"); // 로그인 상태가 아니면 로그인 창으로 이동한다.
		    }
         }else{
            response.sendRedirect("login3.html"); // 세션이 생성되지 않았으면 로그인 창으로 이동한다.
         }
     }
	
}
package sec05.ex01;

import java.sql.Date;

public class MemberVO {
	private String id;
	private String pwd;
	private String name;
	private String email;
	private Date joinDate;
	
	public MemberVO() {
		System.out.println("MemberVO 객체 생성");
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getJoinDate() {
		return joinDate;
	}

	public void setJoinDate(Date joinDate) {
		this.joinDate = joinDate;
	}
	
}

6. 로그인 창에서 ID와 비밀번호를 입력한 후 전송한다.

- http://localhost:8090/pro09/login3.html

7. 회원 테이블에 입력한 ID와 비밀번호가 존재하면 로그인 성공 메시지가 출력되고, 없으면 실패 메시지가 출력된다.

8. 회원정보보기를 클릭하면 이미 로그인 상태이므로 세션에 저장된 회원 정보가 표시된다.

9. 다음은 톰캣을 재실행한 후 로그인창을 거치지 않고 바로 /show로 요청한 경우이다. 로그인을 하지 않았으므로 다시 로그인창으로 리다이렉트 된다.

* 현재 웹 사이트들은 사용자가 로그인하면 한 번만 데이터베이스에서 회원 정보를 조회한 후 로그인 상태를 세션에 저장해 놓고, 각 웹 페이지를 열 때마다 세션에 접근해 앞 페이지에서 로그인을 했는지 판단한다.