[boostcourse] 5.4. Cookie & Session - BE

1. 웹에서의 상태 유지 기술

- HTTP 프로토콜은 상태 유지가 되지 않는 프로토콜

- 웹 브라우저(클라이언트)의 요청에 대한 응답을 하고 나면, 해당 클라이언트와의 연결을 지속하지 않음

- 상태 유지를 위해 Cookie와 Session 기술이 등장함

쿠키

- 사용자 컴퓨터에 저장

- 저장된 정보를 다른 사람 또는 시스템이 볼 수 있는 단점

- 유효시간이 지나면 사라짐

 

세션

- 서버에 저장

- 서버가 종료되거나 유효시간이 지나면 사라짐

 

쿠키의 동작 이해

1. 클라이언트가 WAS에게 요청

2. WAS는 쿠키 생성

3. WAS는 쿠키를 포함하여 클라이언트에게 응답함

4. 클라이언트는 다음 요청 시 쿠키를 포함하여 요청함

5. WAS는 쿠키를 받아서 검사함

 

세션의 동작 이해

1. 클라이언트가 WAS에게 요청

2. WAS는 세션 키 생성

3. 세션 키를 이용한 저장소 생성

4. 세션 키를 담은 쿠키 생성

5. WAS는 쿠키를 포함하여 클라이언트에게 응답함

6. 클라이언트는 다음 요청 시 세션 키를 저장하고 있는 쿠키를 포함하여 요청함

7. WAS는 쿠키의 세션 키를 이용해 이전에 생성한 저장소를 활용

 

2. 쿠키를 이용한 상태 유지

쿠키 정의

정의

- 클라이언트 단에 저장되는 작은 정보의 단위

- 클라이언트에서 생성하고 저장될 수 있고, 서버단에서 전송한 쿠키가 클라이언트에 저장될 수 있음

 

이용 방법

- 서버에서 클라이언트의 브라우저로 전송되어 사용자의 컴퓨터에 저장

- 저장된 쿠키는 다시 해당하는 웹 페이지에 접속할 때, 브라우저에서 서버로 쿠키를 전송

- 쿠키는 이름(name)과 값(value)으로 구성된 자료를 저장

- 이름 외에도 comment, path, max-age, expire, version, domain과 같은 추가적인 정보를 저장

 

제한

- 하나의 쿠키는 4KByte 크기로 제한

- 브라우저는 각각의 웹 사이트당 20개의 쿠키를 허용

- 모든 웹 사이트를 합쳐 최대 300개를 허용

- 그러므로 클라이언트당 쿠키의 최대 용량은 1.2MByte

 

jakarta.servlet.http.Cookie

1. 서버에서 쿠키 객체 생성, Response의 addCookie 메소드를 이용해 클라이언트에게 전송

Cookie cookie = new Cookiew(name, value);

response.addCookie(cookie);

 

2. 쿠키는 이름, 값의 쌍 정보로 입력하여 생성

- 쿠키의 이름은 알파벳과 숫자로만 구성되고, 쿠키 값은 공백, 괄호, 등호, 콤마, 콜론, 세미콜론 등은 포함 불가능

 

3. 클라이언트가 보낸 쿠키 정보 읽기

Cookie[] cookies = request.getCookies();

- 쿠키 값이 없으면 null이 반환됨

- 쿠키가 가지고 있는 getName()과 getValue() 메소드를 이용해서 원하는 쿠키 정보를 찾아 사용함

 

4. 클라이언트에게 쿠키 삭제 요청

- 클라이언트가 가진 쿠키를 서버가 삭제하는 명령은 없고, 같은 이름의 쿠키를 만들고 max-age를 0으로 설정해 쿠키를 전송함

Cookie cookie = new Cookiew(name, null);

cookie.setMaxAge(0);

response.addCookie(cookie);

 

5. 쿠키의 유효기간 설정

- setMaxAge()

- 인자의 유효기간을 나타내는 초 단위의 정수형

- 유효기간 == 0: 쿠키 삭제, 유효기간 < 0: 브라우저가 종료될 때 쿠키 삭제

 

6. Spring MVC에서의 Cookie 사용

- @CookieValue 애노테이션 사용

- 컨트롤러 메소드의 파라미터에서 @CookieValue를 사용해 원하는 쿠키 정보를 파라미터 변수에 담아 사용할 수 있음

ControllerMethod(@CookieValue(value = "쿠키이름", required=false, defaultValue="기본값") String 변수명)

 

3. 쿠키 실습하기

Guestbook 프로젝트 활용

- 방문한 수를 쿠키에 저장해서 보여주기

# GuestbookController.java

package com.example.guestbook.controller;

import com.example.guestbook.dto.Guestbook;
import com.example.guestbook.service.GuestbookService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.ArrayList;
import java.util.List;

@Controller
public class GuestbookController {

    @Autowired      // Service를 사용하기 위해 주입
    GuestbookService guestbookService;

    @GetMapping("/list")
    public String list(@RequestParam(name = "start", required = false, defaultValue = "0") int start,
                       ModelMap model, HttpServletRequest request, HttpServletResponse response) {

        // 방문자 수 집계를 위한 쿠키 처리
        String value = null;
        boolean find = false;
        Cookie[] cookies = request.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                if("count".equals(cookie.getName())) {
                    find = true;
                    value = cookie.getValue();
                    break;
                }
            }
        }

        if(!find) {
            value = "1";
        }
        else {
            try {
                int i = Integer.parseInt(value);
                value = Integer.toString(++i);
            } catch (Exception e) {
                value = "1";
            }
        }

        Cookie cookie = new Cookie("count", value);
        cookie.setMaxAge(60 * 60 * 24 * 365);       // 1년 유지
        cookie.setPath("/");        // 경로 이하에 모든 쿠키 적용
        response.addCookie(cookie);

        // start로 시작하는 방명록 목록 구하기
        List<Guestbook> list = guestbookService.getGuestbooks(start);

        // 전체 페이지 수 구하기
        int count = guestbookService.getCount();
        int pageCount = count / GuestbookService.LIMIT;
        if(count % GuestbookService.LIMIT > 0) pageCount++;

        // 페이지 수만큼 start의 값을 리스트로 저장
        // 페이지 수가 3이라면, 0, 5, 10 저장
        List<Integer> pageStartList = new ArrayList<>();
        for(int i = 0; i < pageCount; i++) {
            pageStartList.add(i * GuestbookService.LIMIT);
        }

        model.addAttribute("list", list);
        model.addAttribute("count", count);
        model.addAttribute("pageStartList", pageStartList);
        model.addAttribute("cookieCount", value);

        return "list";      // list.jsp로 넘겨줌
    }

    @PostMapping("/write")
    public String write(@ModelAttribute Guestbook guestbook, HttpServletRequest request) {
        String clientIp = request.getRemoteAddr();
//        System.out.println("clientIp: " + clientIp);
        guestbookService.addGuestbook(guestbook, clientIp);
        return "redirect:list";
    }
}

 

Spring MVC의 @CookieValue 이용하여 쿠키 값 읽어오기

# GuestbookController.java

@Controller
public class GuestbookController {

    @Autowired      // Service를 사용하기 위해 주입
    GuestbookService guestbookService;

    @GetMapping("/list")
    public String list(@RequestParam(name = "start", required = false, defaultValue = "0") int start,
                       ModelMap model, @CookieValue(value="count", defaultValue = "0", required = true) String value,
                       HttpServletResponse response) {

        // 방문자 수 집계를 위한 쿠키 처리
        try {
            int i = Integer.parseInt(value);
            value = Integer.toString(++i);
        } catch (Exception e) {
            value = "1";
        }

        Cookie cookie = new Cookie("count", value);
        cookie.setMaxAge(60 * 60 * 24 * 365);       // 1년 유지
        cookie.setPath("/");        // 경로 이하에 모든 쿠키 적용
        response.addCookie(cookie);

 

4. 세션을 이용한 상태 유지

세션 정의

정의

- 클라이언트 별로 서버에 저장되는 정보

 

이용 방법

- 웹 클라이언트가 서버 측에 요청을 보내게 되면 서버는 클라이언트를 식별하는 session id를 생성함

- 서버는 session id를 이용해서 key와 value를 이용한 저장소인 HttpSession을 생성함

- 서버는 session id를 저장하고 있는 쿠키를 생성하여 클라이언트에 전송함

- 클라이언트는 서버측에 요청을 보낼때 session id를 가지고 있는 쿠키를 전송함

- 서버는 쿠키에 있는 session id를 이용해서 이전 요청에서 생성한 HttpSession을 찾고 사용함

 

세션 생성 및 얻기

HttpSession session = request.getSession();

HttpSession session = request.getSession(true);

- request의 getSession() 메소드는 서버에 생성된 세션이 있다면 세션을 반환하고, 없다면 새롭게 세션을 생성하여 반환함

- 새롭게 생성된 세션인지는 HttpSession이 가지고 있는 isNew() 메소드를 통해 알 수 이음

 

HttpSession session = request.getSession(false);

- request의 getSession() 메소드에 파라미터로 false를 전달하면, 이미 생성된 세션이 있다면 반환하고 없으면 null을 반환함

 

세션에 값 저장

session.setAttribute(String name, Object value)

- name과 value의 쌍으로 객체 Object를 저장하는 메소드

- 세션이 유지되는 동안 저장할 자료를 저장

 

세션에 값 조회

String value = (String)session.getAttribute(String name)

- 세션에 저장된 자료는 다시 getAttribute(String name) 메소드를 이용해 조회

- 반환 값은 Object 유형이므로 저장된 객체로 자료 유형 변환이 필요함

 

세션에 값 삭제

removeAttribute(String name) 메소드

- name 값에 해당하는 세션 정보를 삭제함

invalidate() 메소드

- 모든 세션 정보를 삭제함

 

jakarta.servlet.http.HttpSession

- 세션은 클라이언트가 서버에 접속하는 순간 생성

- 특별히 지정하지 않으면 세션의 유지 시간은 기본 값으로 30분 설정

- 세션의 유지 시간이란, 서버에 접속한 후 서버에 요청을 하지 않는 최대 시간

- 30분 이상 서버에 전혀 반응을 보이지 않으면 세션이 자동으로 끊어짐

- 세션 유지 시간은 web.xml 파일에서 설정 가능

<session-config>

    <session-timeout>30</session-timeout>

</session-config>

 

5. 세션 실습하기

요구사항

- /guess로 요청을 하면 컴퓨터가 1부터 100 사이의 임의의 값 중의 하나를 맞춰보라는 메시지를 출력함

- 해당 값은 세션에 저장함

- 사용자는 1부터 100 사이의 값을 입력함

- 입력한 값이 세션 값보다 작으면, 입력한 값이 작다고 출력

- 입력한 값이 세션 값보다 크면, 입력한 값이 크다고 출력

- 입력한 값이 세션 값과 같다면 몇 번째에 맞췄다고 출력

 

# GuessNumberController.java

package com.example.guestbook.controller;

import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class GuessNumberController {

    @GetMapping("/guess")
    public String guess(@RequestParam(name="number", required = false) Integer number, HttpSession session, ModelMap model) {

        String message = null;

        if(number == null) {
            session.setAttribute("count", 0);
            session.setAttribute("randomNumber", (int)(Math.random()) * 100 + 1);
            message = "내가 생각한 숫자를 맞춰보세요!";
        }
        else {
            int count = (Integer)session.getAttribute("count");
            int randomNumber = (Integer)session.getAttribute("randomNumber");

            if(number < randomNumber) {
                message = "입력한 값은 내가 생각하고 있는 숫자보다 작습니다.";
            }
            else if(number > randomNumber) {
                message = "입력한 값은 내가 생각하고 있는 숫자보다 큽니다.";
            }
            else {
                message = "OK " + ++count + "번째 맞췄습니다. 내가 생각한 숫자는 " + number + "입니다.";
                session.removeAttribute("count");
                session.removeAttribute("randomNumber");
            }

            model.addAttribute("message", message);
        }

        return "guess";
    }
}

 

# guess.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>숫자 맞추기 게임</title>
</head>
<body>
<h1> 숫자 맞추기 게임.</h1>
<hr>
<h3>${message }</h3>

<c:if test="${sessionScope.count != null}">
    <form method="get" action="guess">
        1부터 100사이의 숫자로 맞춰주세요.<br>
        <input type="text" name="number"><br>
        <input type="submit" value="확인">
    </form>
</c:if>

<a href="guess">게임 다시 시작하기.</a>
</body>
</html>