관심쟁이 영호

[Spring Boot] 쿠키, 세션 본문

Bank-End/Spring Boot

[Spring Boot] 쿠키, 세션

관심쟁이 영호 2021. 8. 3. 15:40
반응형

오늘은 로그인에서 쿠키 및 세션을 적용해볼 것이다!

 

목차

  • 쿠키란?
  • 쿠키구현
  • 쿠키의 한계
  • 세션이란?
  • 세션구현
  • 스프링에서의 세션
  • 세션의 한계

쿠키란?

  • 쿠키는 서버측에서 사용자가 누구인지 알기위한 식별값이다.
  • TCP/IP 위의 HTTP 통신이 지속성이 없기 때문에(한번 요청 - 한번 응답 끝) 사용자가 요청했을 때, 서버측에서는 그 사용자가 누구인지 모른다.
  • 서버는 사용자가 요청을하면 쿠키를 생성하여 응답에 포함하여 전달한다.

 

그림으로 살펴보자.

 

1) 최초의 Request - Response

 

  1. 사용자가 서버측에 Post요청을 한다.
  2. 서버는 회원 저장소에서 로그인 id에 맞는 정보가 있는지 확인한다.
  3. 없어서 쿠키를 설정하고(Set-Cookie: id=abc) 응답한다.
  4. 회원은 웹브라우저에 있는 쿠키 저장소에 쿠키를 저장한다.

 

2) 2번째 요청(쿠키가 유지되었을 때)

 

  1. 사용자는 쿠키저장소에 저장되어있는 쿠키를 포함한 요청을 한다.
  2. 서버는 해당 쿠키가 회원저장소에 있는지 살펴본다.
  3. 회원저장소에 해당 쿠키가 존재하여 해당 회원에 대한 응답을 해준다.

 

위의 내용을 보면 서로 지속성이 없는대도 요청자가 누구인지 알 수 있게 되었다!

 

그럼 Spring에서 해당 쿠키를 어떻게 구현하는지 살펴보자.

 


먼저 가장 쉬운방법은 컨트롤러에서 쿠키를 생성하여 Response에 담아서 응답해주면 될 것이다!

 

코드로 쿠키를 생성하여 응답하는 것을 살펴보자.

 

@PostMapping("/login")
    public String loginId(@ModelAttribute User user, HttpServletResponse response) {
        if(!loginService.login(user)){
            return "login";
        }
		
        //쿠키 생성
        Cookie idCookie = new Cookie("userId", String.valueOf(user.getUserId()));
        
        //응답에 쿠키 추가
        response.addCookie(idCookie);
        
        return "redirect:/";
    }

 

코드 설명:

 

Cookie idCookie = new Cookie("userId", String.valueOf(user.getUserId()));

- Cookie를 key: "userId" / value: "사용자가 입력한 id" 로 생성한다.

 

reponse.addCookie(idCookie);

- 응답에 쿠키를 추가한다.

 

이제 쿠키가 정상적으로 브라우저에 포함이 된지 확인해보자!

 

Response 내용

 

쿠키가 Response에 "userId"로 전달된 것이 보인다.

문자가 깨지는 것은 필자가 한글로 쿠키를 전달했기 때문이다!

 

 

웹브라우저 Cookie 저장소

 

Application -> Cookies에 들어가면

key: "userId" / value: "영호" / 그 외 기타 등등

이라는 내용의 쿠키가 저장된 것을 볼 수 있다!

 


쿠키의 한계

지금까지 알아온 쿠키는 엄청 큰 한계점이 있다!

"만약 사용자가 쿠키의 value값을 '영호' -> '철수' 로 교체하여 보낸다면?"

서버는 철수가 들어온지 알고, 철수에 대한 응답을 할 것이다.

 

맞다. 쿠키는 사용자의 로컬에 저장되기 때문에 얼마든지 조작 및 변경이 가능하다.

그래서 쿠키를 사용한다면 반드시 변경되어도 상관없는 정보만 포함하여야 한다!

 


세션이란?

세션은 쿠키의 한계점을 개선한 것이다.

쿠키가 사용자측에서 관리하는 요소라면, 세션은 서버에서 관리하는 요소라고 생각하면 된다!

 

어떻게 서버에서 관리할까?

- 쉽다. 서버에서 쿠키값을 보내지 않고, 랜덤토큰을 생성하여 사용자에게 보낸다. 서버는 랜덤토큰에 대응하는 실제 값을 관리하는 테이블을 가지고 있다.

- 이제 "abCCDCAD^^&#cd"라는 토큰값을 임의로 고쳐도 다른 사람의 토큰과 겹칠 가능성이 현저히 줄어든다!(거의 0퍼)

 

그림을 통해서 살펴보자.

 

 

살펴보자!

  1. 사용자가 id와 password로 로그인한다.
  2. 서버는 해당 id로 회원저장소를 조회한다.
  3. 조회 내용을 토대로 회원값을 임의의 랜덤값을 만든다!
  4. 랜덤값을 사용자에게 전달한다.

세션 구현

그럼 세션을 구현해보자!

이전의 쿠키와 달라진 점은 다음과 같다.

  • 랜덤값 생성
  • 랜덤값과 userId 매핑 테이블 생성
  • 유저에게 랜덤값을 Response에 포함하여 전달

 

코드를 살펴보자!

 

세션 받기

/**
 * 세션 관리
 */
@Component
public class SessionManager {
    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
    /**
     * 세션 생성
     */
    public void createSession(Object value, HttpServletResponse response) {
        //세션 id를 생성하고, 값을 세션에 저장
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);
        //쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    }
    /**
     * 세션 조회
     */
    public Object getSession(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie == null) {
            return null;
        }
        return sessionStore.get(sessionCookie.getValue());
    }
    /**
     * 세션 만료
     */
    public void expire(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie != null) {
            sessionStore.remove(sessionCookie.getValue());
        }
    }
    private Cookie findCookie(HttpServletRequest request, String cookieName) {
        if (request.getCookies() == null) {
            return null;
        }
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }
}

코드 설명:

대충 3가지의 메소드가 있다.

  1. 세션 생성 
  2. 세션 찾기
  3. 세션 제거

 

세션 생성

세션은 랜덤값을 생성해야 하기 때문에, uuid를 이용하여 생성했다.

 

생성된 uuid를 이용하여 "sessionStore"라는 매핑 테이블에 저장을 해주고, 사용자에게 쿠키로 넘긴다.

 

@PostMapping("/login")
    public String loginId(@ModelAttribute User user, HttpServletResponse response) {
        if(!loginService.login(user)){
            return "login";
        }
        sessionManager.createSession(user, response);
        
        return "redirect:/";
    }

 

이거 또한 복잡하다..

그래서 스프링에서 세션을 아주 편하게 사용할 수 있도록 기능을 제공한다!

 


스프링에서의 세션

@GetMapping("/")
public String homeLoginV3Spring(
 @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)
User loginUser,
 Model model) {
 //세션에 회원 데이터가 없으면 home
 if (loginUser == null) {
 return "home";
 }
 //세션이 유지되면 로그인으로 이동
 model.addAttribute("user", loginuser);
 return "loginhome";
}

 

@SessionAttribute를 이용하면 스프링에서 세션을 아주 편하게 사용할 수 있다.

참고로 해당 어노테이션은 세션을 생성하지 않는다.

 

@SessionAttribute를 이용하면 세션을 찾아주는 번거로운 과정을 알아서 해주는 것을 볼 수 있다.

 

그래서 세션생성을 HTTP 서블릿에 구현되어있는 기능을 사용하거나 직접구현한 내용을 사용하자!

 

HTTP 서블릿의 세션을 적용한 컨트롤러

@PostMapping("/login")
    public String loginId(@ModelAttribute User user, HttpServletRequest request) {
        if(!loginService.login(user)){
            return "login";
        }

        HttpSession session = request.getSession();
        session.setAttribute(SessionConst.LOGIN_USER, user);

        return "redirect:/";
    }

 

SeesionConst 내용

public class SessionConst {
    public static final String LOGIN_USER = "loginUser";
};

 

성공!


세션의 한계

세션또한 한계가 있다. 사용자들이 많아지면 서버는 세션 매핑테이블에 계속해서 저장을 해야하는데, 이 테이블 조차 엄청나게 무거워질 수 있다. 그래서 최근에는 JWT 토큰 정책을 사용한다.

 

이 내용은 다음에 살펴보자!

300x250
Comments