쿠키, 세션

Seung jun Cha·2022년 6월 22일
0

1. 쿠키 생성 방법

1-1 서버

  • 서버가 response를 할 때, 정해진 쿠키가 없는 경우 WAS에서 JSESSIONID 라는 이름으로 쿠키를 생성
    이 쿠키는 브라우저의 메모리에 저장되기때문에 브라우저를 종료하면 삭제됨

1-2 개발자

  • 개발자가 생성한 쿠키는 직접 response에 추가해 주어야하고, 쿠키가 사용되는 특정 도메인이나 경로를 지정할 수 있음

1. 로그인, 로그아웃 처리 - 쿠키

  • 개념 : 사용자의 상태를 유지하기 위해 클라이언트 측에 데이터를 저장하는 방식입니다. 클라이언트가 서버에 요청을 보낼 때마다 해당 쿠키를 함께 전송하여 서버는 클라이언트를 식별하고 상태 정보를 유지할 수 있게 됩니다.

  • 클라이언트에 저장되는 key, value가 들어간 데이터 파일로 서버에서 HTTP Response Header에 Set-Cookie속성을 이용해 클라이언트에 쿠키 제공

  • 이름-값(쿠키이름은 숫자와 알파벳으로만 구성된다), 만료 날짜/시간 (쿠키 저장기간), 경로 정보 포함, 도메인, 보안, HttpOnly 속성

1-1 로그인

  1. 서버에서 로그인에 성공하면 HTTP응답을 담은 쿠키를 웹에 전송하고 웹은 쿠키 저장소에 쿠키를 저장

  2. 웹은 모든 요청을 할 때, HTTP 헤더에 쿠키를 같이 보냄. 따라서 추가 요청을 할 때 쿠키의 값을 참고하여 인증 없이 접근이 가능

  3. 쿠키에는 영속 쿠키와 세션 쿠키가 있다.
    (1)영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
    (2)세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

  • 로그인에 성공하면 쿠키를 생성하고 HttpServletResponse에 담아서 클라이언트에게 전달. 쿠키 이름은 memberId 이고, 값(String타입)은 회원의 id 를 담아둔다. 웹 브라우저는 종료 전까지 회원의 id 를 서버에 계속 보내줄 것이다
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult 
bindingResult, HttpServletResponse response)

//로그인 성공처리 후

//쿠키에 시간 정보를 주지 않으면 세션 쿠키(브라우저 종료시 모두 종료)
 Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId())); 
 response.addCookie(idCookie);
  • 서버에서 Request의 getCookie 메서드로 클라이언트가 보낸 쿠키 정보 읽기. 쿠키 값이 없으면 null이 반환된다. NullPointerException 같은 오류들 발생하지 않게 주의해야한다.
Cookie[] cookies = request.getCookies();
@GetMapping("/")
 public String homeLogin(
 @CookieValue(name = "memberId", required = false) Long memberId,
Model model) {

if (memberId == null) {
 return "home";
 }
 
 //로그인
 Member loginMember = memberRepository.findById(memberId);
 if (loginMember == null) {
 return "home";
 }
 
 model.addAttribute("member", loginMember);
 return "loginHome";
  }
 }

1-2 로그아웃

  • 서버에서 쿠키를 삭제할 수는 없다. 쿠키는 클라이언트에서 관리하기 때문이다. 대신 서버에서 삭제하려는 쿠키와 같은 이름의 쿠키를 생성하고, setMaxAge 메서드로 유효기간을 0으로 지정하여 클라이언트에게 보낸다. 그럼 기존의 쿠키가 새로 받은 쿠키로 바뀌고, 새 쿠키의 유효기간은 0이므로 바로 사라지게 된다. 세션쿠키는 로그아웃 버튼을 누르거나 웹브라우저 종료시 쿠키의 수명을 0으로 설정한다.
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
 expireCookie(response, "memberId");
 return "redirect:/";
}

private void expireCookie(HttpServletResponse response, String cookieName) {
 Cookie cookie = new Cookie(cookieName, null);
 cookie.setMaxAge(0);
 response.addCookie(cookie);
}

1-3 @CookieValue

  • 메소드의 파라미터에서 CookieValue 애노테이션을 사용하여, 원하는 쿠키 정보를 파라미터 변수에 담아 사용할 수 있다.
    로그인 하지 않은 사용자도 홈에 접근할 수 있기 때문에 required = false 를 사용한다.
컨트롤러 메소드(@CookieValue(value="쿠키이름", required=false, defaultValue="기본값") String 변수명)
  • 로그인 쿠키( memberId )가 있는 사용자는 로그인 사용자 전용 홈 화면인 loginHome 으로 보낸다. 추가로 홈 화면에 화원 관련 정보도 출력해야해서 member 데이터도 모델에 담아서 전달한다.
    - 쿠키만 사용하면 보안문제가 심각하다

2. 로그인, 로그아웃 처리 - 세션

  • 개념 : 세션은 애플리케이션에서 사용자의 상태를 유지하기 위해, 서버 측에 데이터를 저장하는 방식입니다. 일반적으로 서버 메모리, 데이터베이스, 파일 시스템 등에 세션을 생성합니다. 웹 애플리케이션의 요구 사항, 예상되는 트래픽 및 데이터 양, 보안 등을 고려하여 결정됩니다.

2-1 세션 동작방식

  • 중요한 정보를 모두 서버에 저장해야 한다. 그리고 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해야 한다.

  • 일정시간동안 세션이 계속 유지되기 때문에, 동시접속자 수가 많은 사이트의 경우 서버에 과부하를 주게 되므로 성능이 저하될 수 있다.

  • 비동기 통신을 하면 세션이 필요없어진다

  1. 아이디와 패스워드를 입력하면 서버에서 확인
  2. 맞으면 회원 저장소에서 해당하는 멤버를 가져옴
  3. 세션저장소에는 로그인에 성공한 회원을 저장하는데(?), UUID로 세션아이디를 key값으로 생성하고 value는 멤버로 세션저장소에 저장 즉, 사용자의 정보를 서버에 저장(stateless 위반)
  4. 클라이언트와 서버의 연결이 계속 유지되기 위해서는 결국 쿠키를 사용해야 하는데, 서버는 클라이언트에 mySessionId(세션의 이름) 라는 이름으로 세션ID만 쿠키에 담아서 전달한다. (회원과 관련된 정보는 전달하지 않고 세션이름과 그 아이디만 전달)
  5. 클라이언트는 세션 아이디가 들어있는 쿠키를 쿠키 저장소에 저장하고, 요청시 쿠키를 서버에 전달한다.
  6. 서버는 쿠키에 들어있는 세션아이디로 세션저장소를 조회해서 정보를 사용

2-2 서블릿 HTTP 세션

  • 서블릿은 세션을 위해 HttpSession이라는 기능을 제공
    HttpSession을 이용하기 위해서는 HttpServletRequest가 필요
@PostMapping("/login")
public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult 
bindingResult, HttpServletRequest request) 
// 쿠키에 들어있는 세션아이디를 조회했을 때, 세션이 있으면 있는 세션 반환, 
없으면 신규 세션 생성
 HttpSession session = request.getSession();
 
 //세션저장소에 로그인 회원 정보 보관
 session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
 return "redirect:/";
}
  • 여기서 궁금한 점 사용자 a도 key를 "user"로 설정하고 사용자 b도 "user"로 설정한다면 같은 key를 사용하는데 어떻게 구분해서 가지고 올까? 로그인해서 처음 서버에 연결을 하면 하나의 세션 아이디가 생성되고 이것은 로그인을 한 사용자의 고유한 세션 아이디가 된다. 로그인한 사용자마다 고유한 세션아이디가 사용되는 것이다. 사용자마다 고유한 세션ID를 부여하고 세션ID를 사용해서 세션 객체를 구별한다.
  • 세션의 create 옵션에 대해 알아보자.
    • request.getSession(true) : 기본값
      쿠키에 들어있는 세션아이디를 조회했을 때, 세션이 있으면 기존 세션을 반환한다.
      세션이 없으면 새로운 세션을 생성해서 반환한다.
    • request.getSession(false)
      세션이 있으면 기존 세션을 반환한다.
      세션이 없으면 새로운 세션을 생성하지 않는다. null을 반환한다.
  • 세션 제거
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {

 //세션을 삭제한다.
 HttpSession session = request.getSession(false);
 if (session != null) {
 session.invalidate();
 }
 return "redirect:/";
}
  • 로그인 컨트롤러
    세션을 찾아서 사용하는 시점에는 create: false 옵션을 사용해서 세션을 생성하지 않아야 한다.
    로그인 화면을 가지고 올 때는 세션을 새로 만들 필요가 없으므로 false로 설정
@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {
 //세션이 없으면 home
 HttpSession session = request.getSession(false);
 if (session == null) {
 return "home";
 }
 
 Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER); 
 //세션에 회원 데이터가 없으면 home
 if (loginMember == null) {
 return "home";
 }
 
 //세션이 유지되면 로그인으로 이동
 model.addAttribute("member", loginMember);
 return "loginHome";
}
  • TrackingModes
    로그인을 처음 시도하면 URL이 다음과 같이 jsessionid 를 포함하고 있는 것을 확인할 수 있다.
http://localhost:8080/;jsessionid=F59911518B921DF62D09F0DF8F83F872

server.servlet.session.tracking-modes=cookie
// URL 전달 방식을 끄고 항상 쿠키를 통해서만 세션을 유지하고 싶으면 다음 옵션을 넣어주면 된다. 
이렇게 하면 URL에 jsessionid 가 노출되지 않는다.

서버 입장에서 웹 브라우저가 쿠키를 지원하는지 하지 않는지 최초에는 판단하지 못하므로, 쿠키 값도 전달하고, URL에 jsessionid도 함께 전달한다. 사용하지 않는 방법이므로 URL에 노출되지 않게 설정한다.

2-3 세션 타임아웃

  • 대부분의 사용자는 로그아웃을 선택하지 않고, 그냥 웹 브라우저를 종료한다. 문제는 HTTP가 비연결성(ConnectionLess)이므로 서버 입장에서는 해당 사용자가 웹 브라우저를 종료한 것인지 아닌지를 인식할 수 없다. 따라서 서버에서 세션 데이터를 언제 삭제해야 하는지 판단하기가 어렵다
    따라서 세션의 종료시점은 세션 생성 시점이 아니라 사용자가 서버에 최근에 요청한 시간을 기준으로 30분 정도를 유지해주는 것이다

  • maxInactiveInterval : 세션의 유효 시간, 예) 1800초, (30분)
    lastAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간, 클라이언트에서 서버로 sessionId ( JSESSIONID )를 요청한 경우에 갱신된다.

server.servlet.session.timeout=60 : 60초, 기본은 1800(30분) ->글로벌설정
session.setMaxInactiveInterval(1800); //1800초 -> 특정 세션설정

LastAccessedTime 이후로 timeout 시간이 지나면, WAS가 내부에서 해당 세션을 제거한다
  • 세션과 쿠키는 클라이언트의 요청과 응답값을 관리하므로 Controller에서 생성,처리

2-4 @SessionAttributes

  • 스프링은 세션을 더 편리하게 사용할 수 있도록@SessionAttributes 을 지원한다. @SessionAttributes 파라미터로 지정된 이름과 같은 이름이 @ModelAttribute에 지정되어 있으면, @ModelAttribute가 설정된 메서드가 반환되는 값을 그 세션에 저장한다.
  1. 컨트롤러 클래스 위에 @SessionAttributes를 선언하고, @ModelAttribute는 메서드 위에 선언한다. 둘의 이름이 같으므로, setUpUserForm 메소드가 반환하는 User 객체는 user라는 이름의 세션에 저장된다.
@Controller
@SessionAttributes("user")
public class LoginController {

  @ModelAttribute("user")
  public User setUpUserForm() {
    return new User();
  }
}
  1. @SessionAttributes를 컨트롤러 클래스 위에 선언하고, 메서드 파라미터로 @ModelAttribute를 불러온다. @SessionAttributes의 이름과 @ModelAttribute의 이름이 같다. 파라미터 user 값이 그 세션에 저장된다.
@Controller
@SessionAttributes("user")
public class LoginController {

  @PostMapping("/dologin")
  public String doLogin(@ModelAttribute("user") User user, Model model) {
    ...
  }
}
  1. 메소드에 @SessionAttribute가 있을 경우, 파라미터로 지정된 이름으로 등록된 세션 정보를 읽어 와 변수에 할당한다.
@GetMapping("/info")
public String userInfo(@SessionAttribute("user") User user {
  ...
  return "user";
}
  1. ) SessionStatus로 @SessionAttributes에 의해 저장된 오브젝트를 제거할 수 있다.
    SessionStatus는 컨트롤러 메서드의 파라미터로 사용할 수 있는 스프링 내장 타입이다. SessionStatus의 setComplete 메서드로 user라는 이름의 세션 값을 제거한다.
@Controller
@SessionAttributes("user")
public class UserController {

  @RequestMapping(value="/user/add", method=RequestMethod.POST)
  public String submit(@ModelAttribute("user") User user, SessionStatus sessionStatus) {
    ...
    sessionStatus.setComplete();
    ...
  }
}

세션이 있는지, 세션아이디에 맞는 객체가 있는지 다 자동으로 체크해줌
@SessionAttributes는 세션을 생성하는 기능은 없음

2-5 ArgumentResolver 사용

public class LoginArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        boolean hasLoginAnnotation = methodParameter.hasParameterAnnotation(Login.class);
        boolean hasLongType = Long.class.isAssignableFrom(methodParameter.getParameterType());
        return hasLoginAnnotation && hasLongType;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        HttpSession session = request.getSession(false);
        if(session == null){  //세션이 없으면 null 반환
            return null;
        }
        return session.getAttribute("userId");
    }
}
  • WebConfig에 등록하기
@Configuration
public class WebConfig implements WebMvcConfigurer {

    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new LoginArgumentResolver());
    }
}

3 세션과 쿠키의 차이점

  • 세션(Session)과 쿠키(Cookie)는 웹 애플리케이션에서 사용자의 상태를 유지하기 위한 메커니즘이다.

    • 세션 : 클라이언트에 고유한 세션 ID를 부여하고, 이를 통해 클라이언트와 관련된 데이터를 서버에 저장한다. 저장용량은 DB나 메모리에 따라 달라진다. 클라이언트가 비활성 상태일 때 자동으로 만료될 수 있다. 만료 시간은 서버에서 설정할 수 있다.

    • 쿠키 : 클라이언트의 웹 브라우저에 저장되며, 클라이언트의 요청 시에 서버로 전송된다. 데이터 형태는 Key, Value 한 쌍으로 구성되고 String 형태이다.

-참고
https://backendcode.tistory.com/176
https://enai.tistory.com/29

0개의 댓글