쿠키 & 세션

유요한·2023년 12월 4일
0

Spring Boot

목록 보기
23/25
post-thumbnail

쿠키(cookie)

쿠키는 사용자의 컴퓨터에 저장이 된다. 웹 브라우저가 보관하고 있는 데이터로, 웹 서버에 요청을 보낼 때 쿠키들을 헤더에 담아서 전송한다. 전송이라는 것은 헤더란 부분과 바디라는 부분으로 두 가지로 나뉘는데 바디는 실제 요청에 대한 데이터들을 가지고 있고 헤더에는 그 요청에 대한 설정을 가지고 있다.

  • 쿠키는 클라이언트(브라우저) 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일입니다.

  • 사용자 인증이 유효한 시간을 명시할 수 있으며, 유효 시간이 정해지면 브라우저가 종료되어도 인증이 유지된다는 특징이 있습니다.

  • 쿠키는 클라이언트의 상태 정보를 로컬에 저장했다가 참조합니다.

  • 클라이언트에 300개까지 쿠키저장 가능, 하나의 도메인당 20개의 값만 가질 수 있음, 하나의 쿠키값은 4KB까지 저장합니다.

  • Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있습니다.

  • 쿠키는 사용자가 따로 요청하지 않아도 브라우저가 Request시에 Request Header를 넣어서 자동으로 서버에 전송합니다.

구성요소

  • 이름 : 각각의 쿠키를 구별하는 데 사용되는 이름
  • 값 : 쿠키의 이름과 관련된 값
  • 유효시간 : 쿠키의 유지시간
  • 도메인 : 쿠키를 전송할 도메인
  • 경로 : 쿠키를 전송할 요청 경로

장단점

클라이언트의 일정 폴더에 정보를 저장하기 때문에 서버의 부하를 줄일 수 있다. 데이터가 사용자 컴퓨터에 저장되기 때문에 보안의 위협을 받을 수 있다. 데이터 저장 용량에 한계가 있다.(소용량의 문자열 데이터) 일반 사용자가 브라우저 내의 기능인 "쿠키차단"을 사용하면 무용지물이 된다.

보안 문제

  • 쿠키 값은 임의로 변경 가능

    클라이언트가 쿠키 값을 강제로 변경하면 다른 사용자가 됨

  • 쿠키에 보관된 정보를 훔칠 수 있음

  • 해커가 쿠키를 해킹하면 평생 사용할 수 있다.

    대안

  • 쿠키에 중요한 값을 노출하지 않고 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고 서버에서 토큰과 사용자 id를 매핑해서 인식한다. 그리고 서버에서 토큰을 관리한다.

  • 토큰이 털려도 시간이 지나가면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게 유지한다.

    동작 방식

  1. 클라이언트가 페이지를 요청
  2. 서버에서 쿠키를 생성
  3. HTTP 헤더에 쿠키를 포함 시켜 응답
  4. 브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있음
  5. 같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄
  6. 서버에서 쿠키를 읽어 이전 상태 정보를 변경 할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답

쿠키 생성

Cookie 객체명 = new Cookie("키","값");
response.addCookie(객체명);

쿠키 조회

쿠키 종료

cookie.setMaxAge(0);

쿠키 저장

사용자 컴퓨터에 저장해야 하므로 응답을 통해서 생성한 쿠키를 보내 저장해야 한다.

response.addCookie(쿠키객체);

@GetMapping(value = "/setCookie")
    public String setCookie(HttpServletResponse response,
            @RequestParam("cookieName") String cookieName, @RequestParam("cookieValue") String cookieValue) {

        // cookie 객체 생성
        Cookie rememberCookie = new Cookie(cookieName, cookieValue);
        // 모든 경로에서 사용
        rememberCookie.setPath("/");

        rememberCookie.setMaxAge(60 * 60 * 24 * 30); // 30일
        rememberCookie.setDomain("localhost");
        response.addCookie(rememberCookie);

        return "setCookie";
    }

쿠키 사용

사용자가 요청 때 함께 보내주는 요청헤더에서 쿠키를 꺼내 사용한다.

request.getHeader("Cookie") : 요청에 있는 Header 중에서 Cookie라는 이름의 헤더가 있는지 확인, null 이라면 전송된 쿠키가 없다는 뜻

request.getCookies() : 전송된 쿠키 객체들의 배열
쿠키객체.getName() : 쿠키의 이름(Key)
쿠키객체.getValue() : 쿠키의 값(Value)

@GetMapping("/getCookie")
public String getCookie(HttpServletRequest request,
        @CookieValue(value = "hi") Cookie cookie) {
    Cookie[] cookies = request.getCookies();

    Arrays.asList(cookies).stream()
            .forEach(c -> log.debug(c.getName() + ":" + c.getValue()));

    log.debug("cookie :{}", cookie.getValue());
    return "getCookie";
}

@CookieValue를 이용하면 Cookie 객체를 받을 수 있다.
HttpServletRequest를 이용해서 cookie객체를 꺼낼 수도 있다.

@RequestMapping("/getCookie1")
	public String getCookie1(@CookieValue String useremail, @CookieValue("useremail") String umail) {
		logger.info("실행");
		logger.info(umail);
		return "redirect:/ch05/content";
	}
@RequestMapping("/getCookie2")
	public String getCookie2(HttpServletRequest request) {
		Cookie[] list = request.getCookies();
		for(Cookie cookie:list) {
			if(cookie.getName().equals("useremail")) {
				logger.info(cookie.getValue());
			}
		}
		return "redirect:/ch05/content";
	}

UserController

package com.example.controller;

import com.example.domain.UserDTO;
import com.example.service.UserService;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@Log4j
@RequestMapping("/user/*")
public class UserController {
    @Setter(onMethod_ = @Autowired)
    UserService service;
    // a태그이니까 GetMapping
//    @GetMapping("/join")
//    public void join() {
//    }
//
//    @GetMapping("/login")
//    public void login() {
//    }

    // 배열로 묶어놓은 것
    @GetMapping({"/join","/login"})
    public void replace() {}

    @PostMapping("/join")
    // 자연스럽게 날라온 응답객체를 addCookie로 보내주기 위해서 매개변수로 받는다.
    public String join(UserDTO user, HttpServletResponse resp) {
        if(service.join(user)) {
            Cookie joinId = new Cookie("joinId", user.getUserId());
            // 쿠키 종료
            // 쿠키 expiration 타임
            joinId.setMaxAge(300);
            // 쿠키 저장
            resp.addCookie(joinId);
        }
        // 그냥 return "이름"을 하면 이름에 해당하는 view를 보여주는 것이고
        // return "redirect:/"을 하면 redirect: 오른쪽 주소로 URL요청을 다시 하는 것입니다.
        // 그로 인해 주소가 바뀌고 해당 URL에 속하는 컨트롤러의 함수가 한번 더 호출이 되는 것이다.
        return "redirect:/";
    }
}
    @GetMapping("/ex14")
    public String ex14(@CookieValue("id") String idValue) {
        log.info(idValue);
        return "/sample/ex14";
    }

return "redirect:/";는 무엇인가?

  1. return : 파일 위치를 찾아감

return "/sample/ex07";
→ 이경우는 view 폴더 아래를 기준으로 원하는 파일의 위치로 찾아감으로 써 파일.jsp를 화면에 출력한다.

그렇기 때문에 return "/";을 하면 /WEB-INF/views아래에 있는 /.jsp를 찾아가기 때문에 아래와 같이 찾을 수 없는 것이다.

  1. redirect : 해당 URL을 찾아감

return "redirect:/";
→ redirect 옆에 기재된 주소를 찾아가서 출력

그렇기 때문에 return "redirect:/";을 하면 /URL을 HomeController가 잡아서 index.jsp를 보여주는 것이다.

여기서 redirect와 forward가 있습니다.

리다이렉트는 페이지 전환 주체가 클라이언트이며, 포워드는 페이지 전환 주체가 서버이다. 클라이언트가 주체가 되어 페이지를 전환하는 방법은 접속한 URL이 아닌 다른 URL로 직접 접속하는 방법 밖에 없다. 반대로 서버가 전환의 주체가 되면 URL주소가 바뀌지 않아도 서버 내부의 동작을 통해 다른 응답을 클라이언트에게 내려줄 수 있다.

리다이렉트란?

리다이렉트는 서버에서 클라이언트 요청한 URL에 대한 응답에서 다른 URL로 재접속하라고 명령을 보내는 것을 말합니다. 브라우저가 www.test.com/page1 URL을 웹 서버에 요청한다. 서버는 HTTP 응답 메시지를 통해 "www.test.com/page2로 다시 요청하세요~" 라고 브라우저에게 다른 URL(길, 방향)을 지시하는 것을 리다이렉트라고 합니다.

rediect의 경우는 실제 url주소를 바꾸고 새로운 request와 response를 만들어냅니다.

  • 페이지 자체를 이동하여 처리
  • Web Container는 redirect 명령이 들어오면 웹 브라우저에게 다른 페이지로 이동하라는 명령을 내림
  • 즉, 다른 페이지를 호출해서 이동
  • 웹 브라우저는 URL을 redirect된 주소로 변경
  • 새로운 페이지에서는 request, response 객체가 새롭게 생성 (공유하지 않음)

포워드란?

클라이언트가 한 번 더 요청을 보내도록 하는 리다이렉트와 다르게 포워드는 서버 내부에서 일어나는 호출이다. 클라이언트의 URL에 대한 요청이 들어오면 해당 URL이 다른 URL로 포워딩 된 것이 확인되었을 경우 서버에서 포워딩된 URL의 리소스를 확인하여 클라이언트에 응답합니다. 포워딩이 일어나면 클라이언트 단에서는 아무런 동작을 하지 않으며, 모든 동작을 서버에서 처리합니다. 따라서 클라이언트(웹브라우저)에서 요청한 URL은 물론 요청 정보는 바뀌지 않습니다.

forward의 경우 실제 웹 페이지 url은 바뀌지 않은채 이전 페이지의 request와 response를 공유합니다. forward의 경우는 페이지 url을 바꾸지않고 ui만 바뀌어 request와 response를 이전페이지 forward의 req와 res를 함께 쓸수 있다고 생각하시면 좋습니다.

  • 응답을 위임하여 처리
  • Web Container 차원에서의 페이지 이동만 존재
  • 실제로 웹 브라우저는 다른 페이지로 이동했음을 알 수 없음
  • 그렇기 때문에 웹 브라우저에는 최초에 호출한 URL이 표시
  • 현재 실행중인 페이지와 forward에 의해 호출된 페이지는 request와 resonse 객체를 공유

리다이렉트와 포워드 사용

사용자 정보가 바뀌어버리는 리다이렉트와 요청 정보는 그대로 유지한체 서버 내부의 동작만 바뀌는 포워드는 적절히 사용되어야 합니다. 리다이렉트의 경우 다음과 같은 경우에서 사용된다. 로그인을 한 회원만 볼 수 있는 마이페이지가 있을 때, 로그인 하지 않은 사람이 마이페이지 url로 접속하려 한다고 하자. 이때, 로그인을 하지 않은 회원의 경우 마이페이지에 접속할 수 있는 권한이 없기 때문에 로그인 페이지로 리다이렉트를 걸거나 메인 페이지로 리다이렉트 걸어줄 수 있다. 포워드는 특정 URL에 대해 외부에 공개되지 말아야하는 부분을 가리는데 사용하거나 조회를 위해 사용합니다. 스프링의 경우 /WEB-INF에 있는 view에 대한 정보들이 외부에 직접 공개되지 말아야 할 때 내부에서 포워딩을 통해 /WEB-INF 경로를 가리키도록 한다. 예를들어, example.com/95로 요청을 하면 example.com/WEB-INF/95를 응답하는 형식입니다.


Session

  • 내장객체로서 브라우저마다 한개씩 존재하고, 고유한 SessionID 생성 후 정보를 추출한다.

  • 서버에 중요한 정보를 보관하고 연결을 유지하는 방법

  • 세션은 쿠키를 기반하고 있지만, 사용자 정보 파일을 브라우저에 저장하는 쿠키와 달리 세션은 서버 측에서 관리합니다.

  • 서버에서는 클라이언트를 구분하기 위해 세션 ID를 부여하며 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증상태를 유지합니다.

  • 물론 접속 시간에 제한을 두어 일정 시간 응답이 없다면 정보가 유지되지 않게 설정이 가능 합니다.

  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 됩니다.

  • 즉 동접자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 됩니다.

  • 클라이언트가 Request를 보내면, 해당 서버의 엔진이 클라이언트에게 유일한 ID를 부여하는 데 이것이 세션 ID입니다.

세션 동작 방식

  1. 클라이언트가 서버에 접속 시 세션 ID를 발급 받음
  2. 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있음
  3. 클라리언트는 서버에 요청할 때, 이 쿠키의 세션 ID를 같이 서버에 전달해서 요청
  4. 서버는 세션 ID를 전달 받아서 별다른 작업없이 세션 ID로 세션에 있는 클라언트 정보를 가져와서 사용
  5. 클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답

핵심!
👉 회원과 관련된 정보는 전혀 클라이언트에 전달하지 않음
👉 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달

특징

  • 각 클라이언트에게 고유 ID를 부여

  • 세션 ID로 클라이언트를 구분해서 클라이언트의 요구에 맞는 서비스를 제공

  • 보안 면에서 쿠키보다 우수

  • 사용자가 많아질수록 서버 메모리를 많이 차지하게 됨

장점

보안성이 좋고 저장용량의 한계가 거의 없다. 서버에 데이터를 저장하므로 부하에 걸릴 수 있다. 쿠키보다 세션을 쓰는것이 더 안정적이다.

세션 이용 방법

웹 클라이언트가 서버에게 요청을 보내면 서버는 클라이언트를 식별하는 session id를 생성한다.

서버는 session id로 key와 value를 저장하는 HttpSession을 생성하고, session id를 저장하고 있는 쿠키를 생성하여 클라이언트에게 전송한다.

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

서버는 쿠키의 session id로 HttpSession을 찾는다.

세션 생성

HttpSession session = request.getSession();
HttpSession session = request.getSession(true);
HttpSession session = request.getSession(false);

request의 getSession() 메서드는 서버에 생성된 세션이 있다면 세션을 반환하고, 없다면 새 세션을 생성하여 반환한다. (인수 default가 true) request의 getSession()메서드의 파라미터로 false를 전달하면, 이미 생성된 세션이 있을 때 그 세션을 반환하고, 없으면 null을 반환한다.

세션에 값 저장하기

setAttribute(String name, Object value)

setAttribute는 name, value 쌍으로 객체 Object를 저장하는 메서드다.
세션이 유지되는 동안 저장된다.

세션에 값 조회하기

getAttribute(String name)

getAttribute 메서드로 세션에 저장된 값을 조회할 수 있다.
리턴 타입은 Object이므로 형변환이 필요하다.
메서드 setAttribute에 이용한 name을 알고 있으면 다음과 같이 조회할 수 있다.

String valeu = (String)session.getAttribute("name");

세션 값 삭제하기

removeAttribute(String name)

removeAttribute 메서드로 name 값에 해당하는 세션 정보를 삭제할 수 있다.

invalidate()

invalidate로 모든 세션 정보를 한 번에 삭제할 수 있다.

세션 유지 시간 설정하기

  • 기본적으로 세션은 30분 유지된다.
  • 세션 유지 시간은 서버에 접속한 후 서버에 요청을 하지 않는 최대 시간을 말한다.
  • 30분 이상 서버에 전혀 반응을 보이지 않으면, 세션이 자동으로 끊어진다.
  • 이 세션 유지 시간은 web.xml 파일에서 설정할 수 있다.

web.xml

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

연습하기

    @GetMapping("/ex11")
    public String ex11(HttpServletRequest req) throws Exception {
        HttpSession session = req.getSession();
        String name = "apple";
        session.setAttribute("sessionId", name);
        return "/sample/ex11";
    }
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>session</title>
</head>
<body>
    <h1>Session.setAttribute()</h1>
    <p>
        <!-- session 사용 -->
        name : ${sessionId}
    </p>
</body>
</html>

세션에 setAttribute한게 잘나온 것을 확인할 수 있다.
이번에는 .getAttribute()를 해보자!

    @GetMapping("/ex12")
    public String ex12(HttpServletRequest req) {
        HttpSession session = req.getSession();
        String name = (String)session.getAttribute("sessionId");

        log.info("===================");
        log.info("세션에 저장되어 있는 변수 : " + name);
        log.info("===================");

        name = "banana";
        session.setAttribute("sessionId", name);
        return "/sample/ex12";
    }


비록 한글이 깨졌지만 처음에 저장되어 있는 apple이 log에 나왔고 그후에 저장한 name= "banana"가 잘 출력이 되었습니다.

login.jsp

 <form class="loginForm" name="loginForm" id="loginForm" action="/user/login" method="post">
        <div class="col-12">
          <h3>아이디</h3>
          <input type="text" name="userId" />
        </div>
        <div class="col-12">
          <h3>비밀번호</h3>
          <input type="password" name="userPw"/>
        </div>
        <div class="col-12" style="text-align: center">
          <input type="submit"  value="로그인" class="primary"/>
        </div>
      </form>

UserController

 @PostMapping("/login")
   public String login(String userId, String userPw, HttpServletRequest req) {
       HttpSession session = req.getSession();
       UserDTO loginUser = service.login(userId, userPw);
       if(loginUser != null) {
           session.setAttribute("loginUser", loginUser.getUserId());
       }
       return "index";
   }

index.jsp

 <c:otherwise>
                    <li>${loginUser}님 환영합니다!</li>
                    <li class="current"><a href="/">Home</a></li>
                    <li><a href="/board/list">Board</a></li>
                    <li><a href="/user/logout">Logout</a></li>
                </c:otherwise>

UserController

 @GetMapping("/logout")
    public String logout(HttpServletRequest req) {
        // 삭제
        req.getSession().invalidate();
        return "index";
    }

쿠키와 세션의 차이

  • 쿠키와 세션은 비슷한 역할을 하며, 동작원리도 비슷합니다. 그 이유는 세션도 결국 쿠키를 사용하기 때문입니다.

  • 가장 큰 차이점은 사용자의 정보가 저장되는 위치입니다. 때문에 쿠키는 서버의 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용합니다.

  • 보안 면에서 세션이 더 우수하며, 요청 속도는 쿠키가 세션보다 더 빠릅니다. 그 이유는 세션은 서버의 처리가 필요하기 때문입니다.

  • 보안, 쿠키는 클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있어서 보안에 취약하지만 세션은 쿠키를 이용해서 sessionid 만 저장하고 그것으로 구분해서 서버에서 처리하기 때문에 비교적 보안성이 좋습니다.

  • 라이프 사이클, 쿠키도 만료시간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속해서 정보가 남아 있을 수 있다. 또한 만료기간을 넉넉하게 잡아두면 쿠키삭제를 할 때 까지 유지될 수도 있습니다.

  • 반면에 세션도 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제됩니다. 예를 들어, 크롬에서 다른 탭을 사용해도 세션을 공유됩니다. 다른 브라우저를 사용하게 되면 다른 세션을 사용할 수 있습니다.

  • 속도, 쿠키에 정보가 있기 때문에 서버에 요청시 속도가 빠르고 세션은 정보가 서버에 있기 때문에 처리가 요구되어 비교적 느린 속도를 가집니다.

세션을 사용하면 좋은데 쿠키를 사용하는 이유

세션은 서버의 자원을 사용하기 때문에 무분별하게 만들다보면 서버의 메모리가 감당할 수 없어질 수가 있고 속도가 느려질 수 있기 때문에 쿠키가 유리한 경우가 있습니다.

profile
발전하기 위한 공부

0개의 댓글