Spring의 쿠키,세션
Spring

Spring의 쿠키,세션

1. 개요

  • 이번 글에서는 고객들의 로그인 상태를 지속적으로 유지시키기 위한 방법을 다룬다.
  • 고객들의 정보를 지속적으로 유지시키기 위해서는 쿠키의 개념이 필요하다.
  • 쿠키의 고질적인 문제를 파악하고 이를 해결하기 위한 세션의 등장개념을 이해하도록 하고 스프링에서의 세션을 어떻게 적용하는지 확인해보자

쿠키가 생성되는 전체 흐름

  • 서버에서 로그인에 성공하면 HTTP 응답을 통해 Server에 접속한다.
  • 접속에 성공한 이후 현재 쿠키가 존재한다면 해당 쿠키를 반환하고 없을 경우 Server에서 생성해서 보내준다.

최초 로그인시 서버가 쿠키를 발급해주는 과정

  • 클라이언트가 쿠키를 가지고 있다면 Server에서는 별도의 login 로직을 거치지 않기 때문에 속도, 성능 측면에서 이점을 가져갈 수 있다.

쿠키가 발급 된 이후 서버로부터 로그인하는 과정.

이런 쿠키에는 2가지의 종류가 있다.

  • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
  • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

이런 쿠키에도 단점이 존재한다.

  • 쿠키를 사용해서 로그인 ID를 전달하고 정보를 유지할 수 있으나 보안측면에서 문제가 존재한다.
  • 실제로 웹에서 개발자 도구를 통해 쿠키값을 변경할 수 있으며 이러한 값 변경을 통해서 고객들의 정보가 유출될 수 있다.
  • 또한 쿠키 자체에 개인정보가 있다면 이 정보는 URL을 통해서 노출되게 된다.

이런 문제를 해결하기 위해 등장한 개념이 세션이다

  • 쿠키의 Key값을 통하여 서버에서 정보를 조회하는것을 세션이라고 한다.
  • 쿠키는 Key값만을 저장한다.
  • 서버에서는 쿠키의 Key값을 통하여 조회하면 Value값을 조회할 수 있게 설정한다.
  • 쿠키의 Key값은 암호화된 값으로 설정한다.

  • 세션을 사용하면 기존의 쿠키만 사용하던 아래의 문제에서 발생하는 보안 문제를 극복할 수 있다.
    • 변조 가능한 쿠키값 문제
      • 예상 불가능한 복잡한 세션 Id를 사용한다
    • 쿠키에 보관되던 정보가 해킹될 경우
      • 쿠키의 키값이 해킹되어도 중요한 정보는 서버에서 보관한다.
    • 쿠키가 탈취된 후 해커의 지속적인 사용 문제
      • 해커가 토큰을 해킹해도 시간이 지나면 사용할 수 없도록 서버에서 세션 만료 시간을 활용해 시간 만료시 세션을 제거한다.

스프링에 쿠키 및 세션 적용하기

  • 메인 홈에 Session을 적용한다.
  • @SessionAttribute는 기존의 HttpSession session = request.getSession(false)를 통해 검증하던 방식을 어노테이션 형태로 리팩토링한 코드이다.
  • false는 세션이 없다면 생성을 안해주고 true는 세션이 없을 경우 생성해주는 옵션이다.
    • 두 옵션 모두 기존에 세션이 있을 경우 해당 세션을 사용한다.
    • 현재 HomeController에서는 메인 페이지 이기 때문에 비가입자가 세션을 가진 상태로 접근할 경우 바로 로그인 화면으로 넘겨준다Login화면
    • HomeLogin
//homeLoginV3에서 session과 session의 속성을 지정해주던것을 스프링에서 제공하는 @SessionAttribute로 한 번에 처리한다.
    @GetMapping("/")
    public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model){

        //세션에 회원 데이터가 없으면 home
        if(loginMember == null){
            return "home";
        }

        //세션이 유지되면 로그인으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";
    }

 

  • Login 단계에서 회원 정보가 없을 경우 loginform으로 이동한다.
  • Login단계에서 가입이 되어있고 세션이 존재할 경우 request.getSession에서 true 옵션으로 설정되어 있으므로 세션을 생성하지 않고 기존의 세션을 사용한다.
  • 세션이 존재하지 않을 경우에는 request.getSession에서 true 옵션으로 설정되어 있으므로 세션을 생성한다.Logout화면
@PostMapping("/login")
    public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request){
        if(bindingResult.hasErrors()){
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
        //log.info("login={}, loginMember");

        if(loginMember == null){
            //reject는 글로벌 오류이다.
            bindingResult.reject("loginFalil", "아이디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }
        //로그인 성공 처리
        //true설정시 세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성
        HttpSession session = request.getSession(true);
        //세션에 로그인 회원 정보 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
        return "redirect:/";
    }
  • 기존 세션이 존재하면 세션을 사용하고 없을 경우 request.getSession 에서 false 옵션으로 아무것도 하지 않기 때문에 null을 반환한다.
  • logout 상태이기 때문에 if조건문에서 세션이 존재할 경우 session.invalidate()를 통하여 session을 삭제한다.
  • 세션 사용의 주의점(세션 타임아웃 설정 필요성)
@PostMapping("logout")
    public String logoutV3(HttpServletRequest request){

        //false 설정시 세션이 있으면 있는 세션 반환, 없으면 null 반환
        HttpSession session = request.getSession(false);
        if(session != null){
            session.invalidate();//invalidate를 사용하면 session으로 받아온 세션을 삭제한다.
        }
        return "redirect:/";
    }
  • 세션은 사용자가 로그아웃을 직접 수행해야지만 삭제된다.
  • 그러나 사용자는 로그아웃을 선택하지 않고 그냥 웹 브라우저를 종료한다.
  • 이런 상황에서 웹 브라우저는 HTTP가 비연결성이므로 서버 입장에서는 해당 사용자가 웹 브라우저를 종료한 것인지 아닌지 인식할 수 없다.
  • 따라서 서버에서 세션 데이터를 삭제 해야하는지 말아야 하는지 판단할 수가 없다.
  • 이런 상황에 남아있는 세션을 무한정 보관하면 다음과 같은 문제가 발생한다.
    • 세션과 관련된 쿠키가 탈취 되었을 경우 데이터를 언제 삭제해야 하는지 판단하기 어렵다
    • 남아 있는 세션을 무한정 보관하면 메모리 초과 오류가 발생한다.
      • 세션의 기본 저장 장소가 메모리에 저장되기 때문이다.
  • 이러한 이유 때문에 세션에는 종료 시간이 설정되어있다.
    • 기본값으로 30분(1800초)이 설정되어있다.
    • 기본값을 변경하고 싶다면 application.properties에 아래의 설정 옵션으로 변경해주면 된다.
    • session.setMaxInactiveInterval(1800); //1800초
  • 세션에는 creationTime(생성일시), lastAccessedTime(마지막 접근 시간) 두 가지 시간 옵션이 있다.
  • 현재 시간이 lastAccessedTime 시간보다 크다면 세션을 재발급 해야한다.
    • 간단하게 생각해서 lastAccessedTime이 토근을 재발급을 안 받아도 되는 시간이다.
  • 하지만 사용 시작 시간에서 30분이면 사용자가 30분만다 세션을 새로 받아야 한다.
    • 즉 로그인을 30분마다 계속해야 한다는 의미이다.
  • 이런 문제점을 해결하기 위해 사용자가 서버에 추가 요청이 있을 때 마다 lastAccessedTme 기본세션 설정시간(기본값 30분)으로 갱신한다.
package hello.login.web.session;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Date;

@Slf4j
@RestController
public class SessionInfoController {
    @GetMapping("/session-info")
    public String sessionInfo(HttpServletRequest request){
        HttpSession session = request.getSession(false);

        if(session == null){
            return "세션이 없습니다.";
        }

        //forEanchReamining을 사용하면 ArrayList로 바뀐다.
        //세션 데이터 출력
        session.getAttributeNames().asIterator()
                .forEachRemaining(name -> log.info("session name={}, value={}", name, session.getAttribute(name)));

        log.info("sessionId={}", session.getId());
        log.info("getMaxInactiveInterval={}", session.getMaxInactiveInterval());
        log.info("creationTime={}", new Date(session.getCreationTime())); //getCreationTime이 long타입인데 new Date로 생성하면 long타입으로 생성이된다.
        log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
        log.info("isNew={}", session.isNew()); //새로 생성된 세션인지, 아니면 과거에 만들어진것인지 여부 확인

        return "세션 출력";
    }
}
  • 세션 정보를 확인하기 위한 추가 컨트롤러 예제
  • 예제의 모든 코드

https://github.com/NamHyeop/Spring_Boot_Study/tree/master/4.spring-MVC2/logi

REFERENCE

0.https://www.inflearn.com/course/스프링-mvc-2/dashboard

'Spring' 카테고리의 다른 글

Spring의 예외처리와 오류페이지  (0) 2022.07.04
Spring의 필터와 인터셉터  (0) 2022.07.01
Spring의 Bean Validation  (0) 2022.06.28
Spring 메시지, 국제화  (0) 2022.06.27
Spring의 Validation이란  (0) 2022.06.27