어제 포스팅에서 이미 세션과 쿠키에 대한 전반적인 내용을 다뤘다. 오늘은 지난 번에 언급했던 것처럼, 이 둘의 차이점에 대해서 아주 조금 더 자세히 살펴보도록 하겠다.
1. 쿠키와 세션 비교
Http 프로토콜에서 상태를 지속하지 않음으로 웹을 통한 효울적인 서비스를 할 수 있지만, 그런 특징으로 인해 상태정보에 대한 지속적인 보전이 어렵게 되어 쿠키나 세션을 사용하게 되었다고 이미 설명했다.
쿠키와 세션에 있어서 가장 큰 특징은 어디에 존재하는가에 대한 차이가 가장 두드러진다. 쿠키는 클라이언트에 저장이 된다는 점과 세션은 서버 측에 저장이 되어 있다는 사실이다. 그래서 클라이언트 측에 있는 사용자는 저장된 쿠키를 열어볼 수 있다. 하지만 서버 측에 있는 세션에 관한 정보는 클라이언트 측에서 접근할 수 없는 영역에 있게 되어 보안에 유리하다.
다음 그림을 통해서 쿠키와 세션의 차이점에 대해서 알아보겠는데, 먼저 쿠키에 대한 내용이다.
그림에서 보는 것처럼 클라이언트에서 서버로 첫 요청을 할 때는 쿠키는 아직 존재하지 않는다. 하지만 요청을 받은 서버는 현재 접속한 클라이언트가 첫 요청을 한 클라이언트인지 여러 번 접속을 한 클라이언트인지를 알 수 없다. 다만 요청에 쿠키 정보가 있는지 없는지를 알아본 후에 쿠키에 대한 정보가 없다면 쿠키를 서버에서 우선 생성한다. 그리고 쿠키에 필요한 값을 넣어둔 후 클라이언트로 응답할 때 쿠키를 같이 전송한다. 클라이언트에서는 서버로부터 받은 요청에 쿠키를 뽑아내서 자신의 저장 공간에 쿠키를 저장한다.
다음으로 저장된 쿠키를 사용하는 그림을 보겠다.
첫 요청에 대한 응답을 받은 클라이언트는 쿠키를 갖고 있다. 이후에 서버로 요청을 할 때는 쿠키 정보를 실어 요청을 한다.
요청을 받은 서버는 요청에 쿠키정보가 있는지 없는지를 조사한다. 만약 쿠키정보가 있다면 그 쿠키에 필요한 정보를 쓰거나, 새로운 쿠키를 생성할 수도 있다. 물론 새로 생성된 쿠키나 새로 변경된 값은 요청한 클라이언트로 넘겨주기 위함이다. 요청에 대한 응답을 받은 클라이언트는 그 속에 포함된 쿠키정보를 읽어 새로운 쿠키가 있으면 저장하고 기존의 쿠키값을 변경시켜 저장하게 된다.
이제 세션의 경우이다.
클라이언트가 서버에 접속되어 요청을 한다. 각각의 클라이언트의 브라우저에 임의의 값을 부여해서 서버가 갖고 있다. 이후에 들어오는 요청에 대해 서버가 갖고 있는 브라우저 정보와 비교를 해서 동일한 클라이언트인지를 판별하게 되는데, 쿠키와 반대로 특정 클라이언트의 정보는 서버에 저장이 되어 있다. 첫 요청 발생 시 서버에서는 ID를 생성한다. 그런 후 쿠키가 저장되는 방식과 마찬가지로 클라이언트에 ID만이 저장된다. 이후의 요청이 발생되었던 ID만이 서버로 보내는 요청에 포함이 된다.
2. 웹 애플리케이션에 있어서 세션과 쿠키의 사용
이번에는 쿠키와 세션을 직접 다루어보는 예제를 작성해 보겠다. 쿠키와 세션의 대표적인 실제 사례는 아마 로그인, 로그아웃을 구현한 서비스에 가장 많은 사용이 될 것이다. 또한 장바구니 등의 서비스를 구현한 사이트에서도 많이 사용된다. 여기서 보이는 예는 데이터베이스와 연동하여 로그인 페이지를 거친 사용자가 회원인지 아닌지에 따라 회원일 경우 로그인 처리가 완료되어 로그인된 페이지로 이동한다. 만약 회원이 아니라면 회원테이블에 입력한 아이디와 패스워드가 없기 때문에 다시 로그인 페이지로 이동된다.
회원일 경우 테이블에 회원 아이디와 패스워드가 있기 때문에 입력받은 아이디와 패스워드가 일치할 경우, 로그인 완료된 페이지로 이동을 한다. 테이블에 회원 이이디가 존재할 경우는 회원을 의미하는 것이기 때문에 이후 방문할 페이지는 회원이 볼 수 있게끔 회원 아이디를 쿠키나 세션에 저장해서 어떤 페이지에서도 회원에 대한 아이디를 참조할 수 있다.
1) 쿠키와 세션에 공통적으로 사용할 자바빈즈 작성
(1) 쿠키
[DBconnectionMgr.java]
package PoolPack;
import java.sql.*;
import java.util.*;
public class DBConnectionMgr {
private Vector connections = new Vector(10);
private String _driver = "com.mysql.cj.jdbc.Driver",
_url = "jdbc:mysql://localhost:각자의 port/각자의 데이터베이스 이름",
_user = "root",
_password = "각자의 비번";
private boolean _traceOn = true;
private boolean initialized = false;
private int _openConnections = 10;
// 싱글턴 패턴(Singleton Pattern)
private static DBConnectionMgr instance = null;
public DBConnectionMgr() {
}
/** Use this method to set the maximum number of open connections before
unused connections are closed.
*/
// 싱글턴 문법
// 객체를 일일이 만드는 것이 아니라 getInstance 메소드 안에 따로 만들어 줘서
// 필요할 때마다 해당 메소드를 통해 그 객체를 반환해 줌
// 이때 static을 해 줌으로 따로 객체를 만들지 않아도 접근 가능함
public static DBConnectionMgr getInstance() {
if (instance == null) {
synchronized (DBConnectionMgr.class) {
if (instance == null) {
instance = new DBConnectionMgr();
}
}
}
return instance;
}
public void setOpenConnectionCount(int count) {
_openConnections = count;
}
public void setEnableTrace(boolean enable) {
_traceOn = enable;
}
/** Returns a Vector of java.sql.Connection objects */
public Vector getConnectionList() {
return connections;
}
/** Opens specified "count" of connections and adds them to the existing pool */
public synchronized void setInitOpenConnections(int count)
throws SQLException {
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < count; i++) {
c = createConnection();
co = new ConnectionObject(c, false);
connections.addElement(co);
trace("ConnectionPoolManager: Adding new DB connection to pool (" + connections.size() + ")");
}
}
/** Returns a count of open connections */
public int getConnectionCount() {
return connections.size();
}
/** Returns an unused existing or new connection. */
public synchronized Connection getConnection()
throws Exception {
if (!initialized) {
Class c = Class.forName(_driver);
DriverManager.registerDriver((Driver) c.newInstance());
initialized = true;
}
Connection c = null;
ConnectionObject co = null;
boolean badConnection = false;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
// If connection is not in use, test to ensure it's still valid!
if (!co.inUse) {
try {
// 접속이 이미 된 것들은 닫아줌
badConnection = co.connection.isClosed();
if (!badConnection)
badConnection = (co.connection.getWarnings() != null);
} catch (Exception e) {
badConnection = true;
e.printStackTrace();
}
// Connection is bad, remove from pool
if (badConnection) {
connections.removeElementAt(i);
trace("ConnectionPoolManager: Remove disconnected DB connection #" + i);
continue;
}
c = co.connection;
co.inUse = true;
trace("ConnectionPoolManager: Using existing DB connection #" + (i + 1));
break;
}
}
if (c == null) {
c = createConnection();
co = new ConnectionObject(c, true);
// 비워진 부분은 채워줌
connections.addElement(co);
trace("ConnectionPoolManager: Creating new DB connection #" + connections.size());
}
return c;
}
/** Marks a flag in the ConnectionObject to indicate this connection is no longer in use */
public synchronized void freeConnection(Connection c) {
if (c == null)
return;
ConnectionObject co = null;
// 반복문 돌면서 같은 객체가 있으면 해당 인덱스를 다시 쓸 수 있게끔 해 줌
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
if (c == co.connection) {
co.inUse = false;
break;
}
}
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
// 현재 사용하는 수(10개)에 맞게 제한함
if ((i + 1) > _openConnections && !co.inUse)
removeConnection(co.connection);
}
}
public void freeConnection(Connection c, PreparedStatement p, ResultSet r) {
try {
if (r != null) r.close();
if (p != null) p.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, Statement s, ResultSet r) {
try {
if (r != null) r.close();
if (s != null) s.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, PreparedStatement p) {
try {
if (p != null) p.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, Statement s) {
try {
if (s != null) s.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
/** Marks a flag in the ConnectionObject to indicate this connection is no longer in use */
// synchronized: 동시 접속 제한하는 기능
public synchronized void removeConnection(Connection c) {
if (c == null)
return;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
if (c == co.connection) {
try {
c.close();
connections.removeElementAt(i);
trace("Removed " + c.toString());
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
private Connection createConnection()
throws SQLException {
Connection con = null;
try {
if (_user == null)
_user = "";
if (_password == null)
_password = "";
Properties props = new Properties();
props.put("user", _user);
props.put("password", _password);
con = DriverManager.getConnection(_url, props);
} catch (Throwable t) {
throw new SQLException(t.getMessage());
}
return con;
}
/** Closes all connections and clears out the connection pool */
public void releaseFreeConnections() {
trace("ConnectionPoolManager.releaseFreeConnections()");
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
if (!co.inUse)
removeConnection(co.connection);
}
}
/** Closes all connections and clears out the connection pool */
public void finalize() {
trace("ConnectionPoolManager.finalize()");
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.get(i);
try {
co.connection.close();
} catch (Exception e) {
e.printStackTrace();
}
co = null;
}
connections.removeAllElements();
}
private void trace(String s) {
if (_traceOn)
System.err.println(s);
}
}
// Wrapping Class
class ConnectionObject {
public java.sql.Connection connection = null;
public boolean inUse = false;
public ConnectionObject(Connection c, boolean useFlag) {
connection = c;
inUse = useFlag;
}
}
src/main/java 폴더 안에 PoolPack이라는 패키지를 만들고 이 안에 위의 코드를 작성하였다.
[RegisterMgr.java]
package PoolPack;
import java.sql.*;
public class RegisterMgr{
private DBConnectionMgr pool;
public RegisterMgr(){
try{
pool = DBConnectionMgr.getInstance();
}catch(Exception e){
System.out.println("Error : 커넥션 연결 실패");
}
}
public boolean loginRegister(String _id, String _pw) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean loginCon = false;
try {
con = pool.getConnection();
String query = "select count(*) from users where id = ? and pw = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, _id);
pstmt.setString(2, _pw);
rs = pstmt.executeQuery();
if(rs.next()&&rs.getInt(1)>0) // 데이터베이스는 인덱스가 1부터 시작하기 때문임
loginCon =true;
}catch(Exception ex) {
System.out.println("Exception" + ex);
}finally{
pool.freeConnection(con,pstmt,rs);
}
return loginCon;
}
}
2) 쿠키를 사용한 로그인
[cookieLogin.jsp]
<%@ page contentType="text/html; charset=EUC-KR"%>
<%
String cookieName = "";
String id = "";
// 생성된 쿠키를 가져옴
Cookie[] cookies = request.getCookies();
// 생성된 쿠키들이 있는 경우에 if문 내부를 수행함
if (cookies != null) {
// 쿠키가 쌓여있는 쿠키배열의 크기만큼 for문을 반복수행함
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("idKey")) {
cookieName = cookies[i].getName();
// idKey란 이름을 가진 쿠키의 값을 id에 담음
id = cookies[i].getValue();
break;
}
}
// id 변수의 값이 공백이 아닐 경우 if문 내부를 수행함
if (!id.equals("")) {
%>
<script>
alert("로그인 되었습니다.");
location.href = "cookieLoginOK.jsp";
</script>
<%
}
}
%>
<html>
<head>
<title>Cookie 로그인</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h2 align="center">Cookie 로그인</h2>
<form method="post" action="cookieLoginProc.jsp">
<table width="300" border="1" align="center" bgcolor="#FFFF99">
<tr bordercolor="#FFFF66">
<td colspan="2" align="center"><b>Log in</b></td>
</tr>
<tr>
<td width="47%" align="center">ID</td>
<td width="53%" align="center"><input name="id"></td>
</tr>
<tr>
<td align="center">PWD</td>
<td align="center"><input name="pwd"></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="login">
<input type="reset" value="reset">
</td>
</tr>
</table>
</form>
</body>
</html>
[cookieLoginProc.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<jsp:useBean id="regMgr" class="PoolPack.RegisterMgr" />
<%
String id = "";
String pwd = "";
if(request.getParameter("id") != null)
id = request.getParameter("id");
if(request.getParameter("pwd") != null)
pwd = request.getParameter("pwd");
if(regMgr.loginRegister(id, pwd)){
Cookie cookie = new Cookie("idKey", id);
response.addCookie(cookie);
%>
<script>
alert("로그인 되었습니다.");
location.href="cookieLoginOK.jsp";
</script>
<% }else{ %>
<script>
alert("로그인 되지 않았습니다.");
location.href="cookieLogin.jsp";
</script>
<% } %>
[cookieLoginOK.jsp]
<%@ page contentType="text/html; charset=EUC-KR"%>
<%
String id = "";
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("idKey")) {
id = cookies[i].getValue();
}
}
if (id.equals("")) {
%>
<script>
alert("로그인 되지 않았습니다.");
location.href = "cookieLogin.jsp";
</script>
<%
}
}
%>
<html>
<head>
<title>Cookie 로그인</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h2 align="center">Cookie 로그인</h2>
<table width="300" border="1" align="center" bgcolor="#FFFF99">
<tr bordercolor="#FFFF66">
<td colspan="2" align="center"><b>Log On Page</b></td>
</tr>
<tr>
<td align="center"><b><%=id%></b>님이 로그인 하셨습니다.</td>
<td align="center"><a href="cookieLogout.jsp">로그아웃</a></td>
</tr>
</table>
</body>
</html>
[cookieLogout.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<%
// 클라이언트의 모든 쿠키를 가져옴
Cookie[] cookies = request.getCookies();
// idKey라는 이름을 가진 쿠키가 있으면 그 쿠키의 생명주기를 '0'으로 만들어 쿠키가 만료된 것으로 설정함
if(cookies!=null){
for(int i=0; i<cookies.length; i++){
if(cookies[i].getName().equals("idKey")){
cookies[i].setMaxAge(0);
response.addCookie(cookies[i]);
}
}
}
%>
<script>
alert("로그아웃 되었습니다.");
location.href = "cookieLogin.jsp";
</script>
[실행결과]
시작 페이지는 이렇게 로그인 페이지로 뜰 것이다. 여기에 데이터베이스 ID와 PWD를 입력해 보겠다. 그럼 결과는?
위와 같은 페이지가 차례대로 나올 것이다.
다음으로 로그아웃을 해 보자.
'로그아웃 되었습니다.'라는 메시지 창이 뜬다. [확인] 버튼을 클릭하면 로그인 페이지로 이동을 한다.
(2) 세션
회원 아이디와 패스워드를 입력받아 테이블에 입력되어 있는 레코드를 검사하여 아이디와 패스워드가 있는지를 확인한 후, 있다면 아이디를 세션에 저장하고 로그인 완료된 페이지로 이동한다. 입력한 아이디와 패스워드가 없다면 다시 로그인 창으로 이동하게 되는 예제이다. 쿠키를 사용한 로그인 처리와 같은 단계를 거친다.
[sessionLogin.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<%
String id = (String)session.getAttribute("idKey");
if(id!=null){
%>
<script>
alert("로그인 되었습니다");
location.href = "sessionLoginOK.jsp";
</script>
<% } %>
<html>
<head>
<title>Simple LogIn</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body bgcolor="#996600" topmargin="100">
<h2 align="center">Session 로그인</h2>
<table width="75%" border="1" align="center" bordercolor="#660000" bgcolor="#FFFF99">
<tr bordercolor="#FFFF99">
<td height="190" colspan="7">
<form method="post" action="sessionLoginProc.jsp">
<table width="50%" border="1" align="center" cellspacing="0" cellpadding="0">
<tr bordercolor="#FFFF66">
<td colspan="2"><div align="center">Log in</div></td>
</tr>
<tr >
<td width="47%"><div align="center">ID</div></td>
<td width="53%"><div align="center"><input name="id"></div></td>
</tr>
<tr>
<td><div align="center">PWD</div></td>
<td><div align="center"><input name="pwd"></div></td>
</tr>
<tr>
<td colspan="2"><div align="center">
<input type="submit" value="login">
<input type="reset" value="reset">
</div></td>
</tr>
</table>
</form></td>
</tr>
</table>
</body>
</html>
[sessionLoginProc.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<jsp:useBean id="regMgr" class="PoolPack.RegisterMgr" />
<%
String id = "";
String pwd = "";
if(request.getParameter("id") != null)
id = request.getParameter("id");
if(request.getParameter("pwd") != null)
pwd = request.getParameter("pwd");
if(regMgr.loginRegister(id, pwd)){
session.setAttribute("idKey",id);
%>
<script>
alert("로그인 되었습니다.");
location.href="sessionLoginOK.jsp";
</script>
<% }else{ %>
<script>
alert("로그인 되지 않았습니다.");
location.href="sessionLogin.jsp";
</script>
<%}%>
[sessionLoginOK.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<%
String id = (String)session.getAttribute("idKey");
if(id == null){
%>
<script>
alert("로그인 되지 않았습니다.");
location.href="sessionLogin.jsp";
</script>
<%}%>
<html>
<head>
<title>Simple LogIn</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body bgcolor="#996600" topmargin="100">
<table width="75%" border="1" align="center" cellpadding="1" cellspacing="1" bordercolor="#660000" bgcolor="#FFFF99">
<tr bordercolor="#FFFF99">
<td height="190" colspan="7">
<table width="50%" border="1" align="center" cellspacing="0" cellpadding="0">
<tr bordercolor="#FFFF66">
<td colspan="2"><div align="center">Log On Page</div></td>
</tr>
<tr >
<td><div align="center">
<strong><%=id%></strong>
님이 로그인 하셨습니다.
</div></td>
<td><div align="center">
<a href="sessionLogout.jsp"><strong>LOG OUT</strong></a>
</div></td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
[sessionLogout.jsp]
<%@ page contentType="text/html; charset=EUC-KR" %>
<% session.invalidate(); %>
<script>
alert("로그아웃 되었습니다.");
location.href="sessionLogin.jsp";
</script>
[실행결과]
결과는 이와 같이 나오고, 로그인하고 로그아웃 하는 부분은 앞서 쿠키와 똑같이 나오므로 생략하겠다.
이것으로 세션과 쿠키와 관련된 포스팅을 마치도록 하겠다.
'JSP&Servlet' 카테고리의 다른 글
계층형 게시판(1) (0) | 2022.04.04 |
---|---|
회원가입 및 로그인 (0) | 2022.03.30 |
세션(Session)과 쿠키(Cookie) (0) | 2022.03.21 |
JDBC로 데이터베이스 연동 (0) | 2022.03.18 |
자바빈즈(JavaBeans) (0) | 2022.03.14 |