✔️ 쿠키와 세션이란?
🔍 쿠키
쿠키란?
- 쿠키는 클라이언트(브라우저) 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일입니다.
- 사용자 인증이 유효한 시간을 명시할 수 있으며, 유효 시간이 정해지면 브라우저가 종료되어도
인증이 유지됩니다. - 클라이언트의 상태 정보를 로컬에 저장했다가 참조합니다.
- Response Header의 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 생성할 수 있습니다.
- 사용자가 따로 요청하지 않아도 브라우저가 Request 시에 Request Header의 Cookie 속성에 자동으로 전송합니다.
쿠키의 구성 요소
- 이름 : 각각의 쿠키를 구벼하는데 사용되는 이름
- 값 : 쿠키의 이름에 매칭되는 값
- 유효시간 : 쿠키 유지시간
- 도메인 : 쿠키를 전송할 도메인
- 경로 : 쿠키를 전송할 요청 경로
쿠키의 동작 방식
- 클라이언트가 페이지 요청
- 서버에서 쿠키 생성
- Http Header에 쿠키를 표함시켜 응답
- 브라우저가 종료되어도 쿠키 만료 기간동안 브라우저가 보관
- 같은 url의 요청이면 Request Header에 쿠키를 전송
- 서버에서 쿠키를 읽어 쿠키를 변경하면 변경된 쿠키를 Http Headere에 포함하여 응답
쿠키 사용 예
아이디와 비밀번호를 저장하시겠습니까?
- 쇼핑몰 장바구니 기능
- 팝업에서
일주일간 이 창을 보지 않음
체크
java 소스로 쿠키 알아보기
간단한 count 증가 기능
먼저 편리하게 스프링 부트로 servlet 셋팅을 하고 아래와 같은 소스로 테스트를 진행하겠습니다.
어려운 코드는 없기 때문에 코드 설명은 하지 않겠습니다.
package com.example.cookiesession;
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 java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/count")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int count = 0;
if (req.getCookies() != null) {
count = Integer.parseInt(req.getCookies()[0].getValue());
}
Cookie cookie = new Cookie("count", String.valueOf(++count)); // 쿠키 생성
resp.addCookie(cookie); // 응답 헤더에 쿠키 셋팅
// 브라우저에 메시지 출력
PrintWriter pw = resp.getWriter();
pw.println("count : " + count);
}
}
참고로 스프링 부트에서 servlet 셋팅을 하기 위해선 아래의 코드가 필요합니다.
package com.example.cookiesession;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class CookieSessionApplication {
public static void main(String[] args) {
SpringApplication.run(CookieSessionApplication.class, args);
}
}
테스트를 진행하겠습니다.
- 브라우저에서 최초로 해당 도메인으로 요청을 합니다.
http://localhost:8080/count
으로 요청하면 아래와 같이 Response Header에만
Set-Cookie 속성에 서버에서 셋팅한 쿠키가 담겼습니다.
하지만 해당 도메인에 최초 요청이기 때문에 Request Header에는 cookie 요청이 없습니다. 이제 브라우저는count
라는 이름의 쿠키에1
이라는 값을 저장하게 됩니다.
브라우저가 종료되어도 만료시간 전에는 살아있습니다.
추가로 쿠키가 저장되어 있는 정보를 보려면 아래 사진을 참고하시면 됩니다.
여기서는 직접 쿠키를 삭제할 수 있고, 삭제하게 되면 실제로 쿠키는 사라지게 되어 다시
1번의 과정을 거치게 됩니다.
- 이제 같은 도메인으로 재요청을 하면 어떻게 될까요?
브라우저는 쿠키를 저장하고 있기 때문에 브라우저는 자동으로 http Request Header에 쿠키를 요청하게 됩니다. 이전에 서버에서 응답해준 쿠키인count=1
을 요청했고,
서버에서는 이를 받아 count를 증가시키는 로직이 있었습니다. 그래서 다시 응답을 해주어서
Response Header에는Set-Cookie
에 또 값이 존재합니다. 서버에서 쿠키를 변경하고, Response Header에 추가했기 때문입니다.
이러한 방식으로 쿠키를 응답하고 요청하게 됩니다. 그러면 계속 count는 증가하게 됩니다.
테스트를 위해 아래 코드로 브라우저에 출력하도록 하였습니다.
// 브라우저에 메시지 출력
PrintWriter pw = resp.getWriter();
pw.println("count : " + count);
- 쿠키를 강제로 지워보겠습니다. 위에 설명한 Application 탭에서 원하는 쿠키를
우클릭하여 삭제하시면 됩니다.
그러면 다시 1번의 과정으로 돌아가게됩니다. 요청 헤더에는 쿠키 요청이 없고(쿠키가 없기때문)
응답 헤더에만 있습니다.
🔍 세션
세션이란?
- 세션과 쿠키는 상충되는 것이 아니라, 쿠키를 기반으로 세션을 사용하게 됩니다.
사용자 정보 파일을 쿠키처럼 브라우저에 저장하지 않고, 브라우저에 실제 데이터를 저장합니다. - 브라우저에는 서버에서 세션을 구분하기 위한 고유 아이디만을 쿠키로 저장합니다.
이는 당연히 서버에서 쿠키를 굽습니다. - 브라우저가 종료되면 세션도 사라집니다. 하지만 종료하지 않아도 제한시간을 두어 일정시간이
지나면 세션이 사라지게 할 수도 있습니다. - 정보가 서버에 있기 때문에 보안에는 좋지만 사용자가 많아지면 메모리 사용량이 늘어나므로
주의가 필요합니다. - 클라이언트가 Reuqest를 보내면, 서버에서 클라이언트(브라우저)에게 고유 ID를 쿠키로 저장하는 것이 세션ID입니다.
세션 동작 방식
- 클라이언트가 서버에 접속 시 서버에서 세션 ID 발급
- 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장한다.
- 클라이언트는 서버에 요청 시, 해당 쿠키의 세션 ID를 서버에 전달한다.
- 서버에서 세션 ID를 이용해 DB나 메모리 등에 있는 실제 데이터를 가져옵니다.
- 클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답해줍니다.
세션의 특징
- 각 클라이언트에게 고유 ID 부여
- 보안면에서 쿠키보다 우수하다.
- 사용자가 많아질수록 메모리 부하
java 소스로 세션 알아보기
간단한 count 증가 기능을 해보겠습니다. 쿠키를 활용해서 했던 내용과 동일한 기능이며,
세션을 이용해서 하는 부분만 다릅니다.
package com.example.cookiesession;
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;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/count")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Integer count = (Integer)session.getAttribute("count");
// count라는 세션이 없으면 count : 1로 세션 생성 있이면 +1을 증가시켜 다시 셋팅
if (session.getAttribute("count") == null) {
session.setAttribute("count", 1);
} else {
session.setAttribute("count", count + 1);
}
PrintWriter pw = resp.getWriter();
pw.println("count : " + session.getAttribute("count"));
}
}
먼저 세션의 특징을 알아보겠습니다.
- 세션은 서버의 메모리에 저장되기 때문에 서버를 정지하면 세션이 초기화됩니다.
브라우저를 껐다켜도 마찬가지입니다. - 세션은 쿠키와 같이쓰입니다. 세션을 셋팅하면 브라우저는 이를 쿠키에 저장합니다.
쿠키로 셋팅했을 때와 다르게 세션으로 셋팅을하면 쿠키명과 값은 암호화로 보여집니다.
고유값을 가지게 됩니다.
최초로 세션 셋팅 시 쿠키와 동일하게 Response Header에만 쿠키를 응답하게 됩니다.
최초에 세션의 세션이름과 세션값을 암호화한 고유값을 셋팅하는 것입니다.
하지만 암호화 되어있다는 점만 다릅니다.
이후 다시 요청을 하게되면, 고유값을 서버에서 체크하여 count를 증가시켜 세션에 셋팅합니다. 하지만 역시 증가된 값을 암호화 되어 브라우저에서 확인할 수 없습니다.
그래서 결과를 보기 위해 서버에서 세션을 화면에 뿌려주었습니다.
Session으로 로그인 구현해보기
이번에는 간단하게 로그인의 흐름이 어떻게 되는지 알아보도록 하겠습니다.
로그인은 세션과 쿠키를 이용하게됩니다. 코드에 집중하기 보단 흐름에 집중해보시면 좋을 것 같습니다. 먼저 코드를 간략히 보겠습니다.
package com.example.cookiesession;
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;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
@WebServlet(name = "sessionLogin", urlPatterns = "/auth/login")
public class SessionLogin extends HttpServlet {
// 로그인 페이지
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
pw.println("<html><body>");
pw.println("<form action='/auth/login' method='post'>");
pw.println("<p>");
pw.println("<input type='text' name='username' placeholder='username'>");
pw.println("</p>");
pw.println("<p>");
pw.println("<input type='password' name='password' placeholder='password'>");
pw.println("</p>");
pw.println("<p>");
pw.println("<input type='submit'>");
pw.println("</p>");
pw.println("</form>");
pw.println("</body></html>");
}
// 로그인 로직
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
resp.setContentType("text/html;charset=utf-8");
String body = getBody(req);
PrintWriter pw = resp.getWriter();
// 로그인 정보 하드코딩
String dbUsername = "monkeydugi";
String dbPassword = "123";
String[] split = body.split("&");
String reqUsername = split[0].substring(9);
String reqPassword = split[1].substring(9);
// 로그인 정보가 맞으면 세션 저장하고 리다이렉트
// 정보가 틀리다면 로그인 페이지 링크 출력
if (dbUsername.equals(reqUsername) && dbPassword.equals(reqPassword)) {
session.setAttribute("nickName", "dugi");
resp.sendRedirect("/welcome");
} else {
pw.println("누구세요? <a href='/auth/login'>login</a>");
}
}
// request body 읽기
public static String getBody(HttpServletRequest request) throws IOException {
String body = null;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
return body;
}
}
doGet 메서드는 단순히 로그인 페이지를 만드는 메서드입니다.
중요한건 doPost인데요. 로그인 페이지에서 Request form body에 요청한 데이터를 읽습니다. 그 후 로그인 정보가 맞다면 nickName이라는 세션을 만들게 되고,
"/welcome" 페이지로 리다이렉트합니다.
로그인 정보가 틀리다면 로그인 페이지 링크를 출력해줍니다.
로그인이 성공했다면 nickName이라는 세션이 생성되어 브라우저에 응답된것입니다.
하지만 정보가 맞지 않더라도 req.getSessin()
으로 세션 정보를 가져오는 것만으로도
Servlet은 세션을 만들게 되어, 브라우저 응답하게 됩니다.
즉 req.getSession()
코드만 있으면 빈 세션이라도 생성하여 브라우저에 응답하게 됩니다.
package com.example.cookiesession;
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;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
@WebServlet(name = "loginWelcome", urlPatterns = "/welcome")
public class LoginWelcome extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
PrintWriter pw = resp.getWriter();
if (session.getAttribute("nickName") != null) {
pw.println("<h1>Hello,");
pw.println(session.getAttribute("nickName"));
pw.println("</h1>");
pw.println("<a href='/auth/logout'>Logout</a>");
} else {
pw.println("<h1>Welcome</h1>");
pw.println("<a href='/auth/login'>Login</a>");
}
}
}
이제 로그인이 성공한 후 위에서 생성한 nickName 세션이 존재하면, nickName값을 보여주고, 없으면 로그인 링크를 뿌려줍니다. 여기서 중요한건 nickName을 세션으로 생성하여 브라우저에 응답해주고, 브라우저는 암호화된 세션을 쿠키에 저장하고, 서버에 요청 시 마다 세션이 저장된 쿠키를 서버에 요청하게 됩니다. 그러면 서버에서 이를 받아 위와 같이 검증하게 됩니다. 결국 한 번 생성한 세션으로 서버가 재기동이 되지 않거나, 브라우저를 끄지 않는다면 로그인을 유지하는 기능을 구현할 수 있게됩니다.
package com.example.cookiesession;
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;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "LogoutWelcome", urlPatterns = "/auth/logout")
public class LogoutWelcome extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.invalidate();
resp.sendRedirect("/welcome");
}
}
그리고 로그아웃을 하게되면 세션을 지워서 /welcome으로 요청 시 다시 브라우저로 빈 세션을 응답하고, 브라우저는 쿠키에 저장되어 /welcome을 으로 요청 시 nickName이라는 세션이 없기 때문에 로그인 페이지를 안내하는 화면을 만나게 됩니다.
여러 브라우저에서 여러 사용자가 접속을 하게된다면, 이와 같이 nickName세션이 있으면 로그인을 시키는 것이 아니고, DB에 있는 사용자 정보를 가져와서 비교하게 되겠죠?
세션을 메모리가 아닌 다른 저장소에 저장하기
현재는 세션이 서버의 메모리에 저장되어 있습니다. 그래서 서버를 재시작하거나, 브라우저를 끄면 세션은 사라지게 됩니다. 하지만 우리는 컴퓨터를 끄고 다시 켜도 로그인이 유지되는 경우가 많죠? 그 이유는 DB나 파일 형식으로 세션 정보를 가지고 있어 서버 메모리가 초기화 되어도
기존에 DB에 있는 세션을 브라우저에게 다시 응답해줄 수 있습니다.
🔍 쿠키와 세션의 차이
- 쿠키와 세션은 비슷한 역할을 하며, 동작원리도 거의 비슷합니다. 그 이유는 세션도 결국 쿠키를 사용하기 때문입니다.
- 가장 큰 차이점은 사용자의 정보가 저장되는 위치입니다. 쿠키는 브라우저에만 자원을 저장하고, 세션은 서버에 저장합니다.
- 쿠키는 브라우저에 쿠키명, 쿠키값을 그대로 유출하지만, 세션은 고유한 ID(Session Id)만 브라우저에
제공할 뿐 값을 알기 위해서는 서버에서 고유한 ID로 찾게됩니다. 즉, 브라우저는 서버에서
고유ID만 받고 그 값으로 요청을하는 방식입니다. - 보안은 당연히 세션이 우수하지만, 서버의 처리가 필요없는 쿠키가 속도는 빠릅니다.
- 쿠키는 로컬에 저장되기 때문에 변질되거나 탈취당할 우려가 있습니다. 반면 서버는 Session Id만 브라우저가 알고있기 때문에 안전합니다.
- 쿠키는 파일로 브라우저에 저장되기 때문에 브라우저를 종료하거나, 서버를 재시작해도 정보가 남아있을 수 있습니다. 하지만 세션은 서버의 메모리에 존재하기 때문에 두 가지 상황에서 초기화됩니다. 단, 세션도 만료시간을 설정할 수 있습니다.
쿠키를 사용하는 이유
세션은 서버의 자원을 사용하기 때문에 서버의 메모리를 많이 사용할 우려가 있어 속도가 느려는 등 여러 상황이 많기 때문입니다.
쿠키와 캐시의 차이는?
캐시
이미지, css와 같은 재활용할 수 있는 작은 파일 정보들을 저장하게 됩니다. 캐시 저장소(임시저장소)라고 브라우저에 따로 존재하게 되는데 여기서 호출하고자 하는 파일이 존재하면 여기서 가져와서 바로 사용하게 됩니다. 즉, 페이지에 있는 수 많은 이미지들을 매번 서버에서 받아오면 로딩 지연되기 때문에 로딩 속도를 빠르게 하기위해 사용됩니다.
저장되는 위치는 캐시히트라는 곳에 저장된다고 합니다.
또한 라이프사이클이 사용자가 수동으로 삭제를 해야만 한다는 점이있습니다.쿠키
위에 세션과 쿠키에 대해서 설명을 했기 때문에 간략하게 설명하겠습니다.
일단 사용자의 정보를 저장하는 용도로 사용됩니다. 장바구니, 방문횟수, 최근 다운로드 내역 등과 같은 정보를 세션 아이디로 보관하여 서버와 통신을 하게 됩니다. 즉 유저에 대한 행동 패턴을 알기위해 사용된다고 보면 됩니다.
브라우저에는 총 300개의 쿠키가 저장 가능하며 하나의 토메인당 20개의 쿠키만 가질 수 있습니다. 초과되면 가장 사용 빈도가 적은 쿠키부터 제거됩니다.
또한 라이프사이클은 캐시와는 다르게 만료시간에 따라 지정할 수 있습니다. 물론 직접 지울수도 있습니다.
참조
https://interconnection.tistory.com/74
https://opentutorials.org/course/2136/12064
'Java' 카테고리의 다른 글
Java로 자료구조 이해하기 2편(Arraylist와 LinkedList) (0) | 2021.10.02 |
---|---|
Java Serializable (0) | 2021.10.02 |
Java로 자료구조 이해하기 1편(Array와 ArryaList의 차이점) (0) | 2021.09.30 |
static메서드에 generic 사용 (0) | 2021.09.24 |
StringBuilder, StringBuffer와 String 차이 (0) | 2021.05.15 |