3 분 소요

로그인을 할 시 다음과 같이 로그인 정보를 세션에 저장하는 코드를 짠다.

@PostMapping("/login")
public String login(@ModelAttribute UserDTO userDTO,HttpSession session, Model model) {
  UserDTO loginResult = userService.login(userDTO);  
    if (loginResult != null) {
      session.setAttribute("loginInformation", loginResult);
      return "main";
    } else {
      model.addAttribute("loginError", "아이디 또는 비밀번호가 일치하지 않습니다.");
      return "login";
      }
    }

그럼 왜 로그인을 할 때 세션에 정보를 저장할까?

먼저, HttpSession이란 무엇인가

HttpSession: 웹 애플리케이션에서 사용자의 상태 정보(예: 로그인 정보, 구매한 상품 목록 등)를 서버 측에서 유지 관리하기 위해 사용되는 객체이다.

HTTP 프로토콜은 기본적으로 상태를 유지하지 않는 특성을 가지고 있어, 클라이언트와 서버 간의 각각의 요청과 응답이 독립적으로 처리된다. 이러한 상태를 유지하지 않는 특성 때문에, 서버는 동일한 사용자로부터 오는 연속된 요청들을 서로 구분하거나 연관시키기 어렵다.

세션에 정보를 저장하는 이유?

  1. 사용자 인증 상태 유지: 사용자가 시스템에 로그인하면, 그 사용자의 로그인 상태를 유지해야 한다. 사용자가 로그인한 상태에서 다른 페이지로 이동하거나, 추가적인 요청을 할 때마다 매번 로그인 정보를 입력하지 않아도 되도록 하기 위해서이다.

  2. 보안: 사용자의 로그인 정보와 같은 중요한 정보를 클라이언트 측(ex:쿠키)에 저장하는 것은 보안상 취약점을 유발할 수 있다. 서버 측에서 세션을 통해 이러한 정보를 관리함으로써 보안성을 향상시킬 수 있다.

  3. 서버 측 상태 관리: 사용자별로 개인화된 서비스를 제공하기 위해 사용자의 상태 정보를 서버 측에서 관리할 필요가 있다. 예를 들어, 사용자별 쇼핑 카트 정보, 언어 설정, 테마 설정 등을 관리할 수 있다.

  4. 효율적인 자원 관리: 세션을 통해 사용자별로 필요한 정보만을 서버 메모리에 유지함으로써 자원을 효율적으로 관리할 수 있다. 사용자가 로그아웃하거나 세션 타임아웃이 발생하면 해당 세션 정보를 제거하여 리소스를 해제할 수 있다.

로그인 시 사용자의 인증 정보를 ‘HttpSession’에 저장함으로써, 사용자가 애플리케이션 내에서 이동할 때마다 해당 사용자가 누구인지를 식별하고, 사용자에게 적절한 서비스와 정보를 제공할 수 있게 된다.

쿠키, 세션, 토큰, JWT

쿠키

서버는 브라우저에 데이터를 저장하여 서버가 클라이언트를 식별하고 유지할 수 있게한다. 클라이언트가 쿠키를 브라우저에 저장하면 브라우저가 서버에 요청을 보낼때 자동으로 쿠키를 함께 보낸다. 서버에 따라 유효기간이 있고, 쿠키는 인증 뿐 아니라 언어설정과 같은 다른 것도 저장할 수 있다. 단순히 세션 id를 전달하는 매개체라고 생각하면 된다.

세션

HTTP프로토콜은 stateless이며, 서버로 가는 모든 요청이 독립적이다. 즉, 요청끼리 연결이 없고 메모리가 없음. 요청이 끝나면 서버는 클아이언트를 잊어버린다. 따라서 요청할 때마다 알려줘야한다. 그럴 때 쓰는게 세션이다. 로그인을 성공하면 서버는 세션BD에 정보를 저장하고, 각각은 세션ID를 가지고 있는데, 이는 쿠키를 통해 브라우저로 들어오고 저장된다. 그럼 서버는 이ID를 가지고 세션DB를 확인하여 클라이언트가 누구인지 알게 된다.

토큰

안드로이드나 ios에서는 세션을 사용할 수 있지만 쿠키는 사용할 수 없음. 따라서 쿠키를 대신해서 사용하는 것이 토큰이다. 토큰은 이상하게 생긴 string형식이며 작동 방식은 쿠키와 비슷하다.

JWT

세션을 사용할 시 로그인한 유저들의 모든 세션 ID는 세션DB에 저장된다. 따라서 사용자가 늘어나게 되면 많은 DB리소스가 필요하다. JWT에서 서버는 유저를 인증하는데 필요한 정보를 토큰에 저장하고, 이를 사용하면 세션DB를 가질 필요가 없다. JWT는 로그인시 단순히 유저의 ID로 정보를 string형태로 보낸다. 서버는 토큰을 받으면 토큰이 유효한지만 확인하면 되고, 서버는 우리를 클라이언트로 인증한다.

오류

어제까지 잘 작동되던 어플리케이션이 오늘 재실행 하니 위와 같은 오류가 뜨며 실행이 제대로 되지 않았다.

SPRING_SECURITY_CONTEXT 세션 속성을 세션 []에 대해 역직렬화할 수 없다고 한다.

근데 역직렬화는 처음 들어보는거라 일단 검색을 해봤다.

역직렬화: 사용자의 인증 정보나 세션 정보는 서버의 메모리에 객체 형태로 저장되어있다. 이 정보를 서버가 재시작하거나 서버 간에 공유하기 위해서는, 객체를 바이트로 변환하여 데이터베이스나 다른 서버로 전송해야 한다. 이 객체를 바이트 변환하는 과정을 직렬화라고 하며, 반대로 이 바이트 시퀀스를 받아 다시 객체로 변환하는 과정을 역직렬화라고 한다.

나중에 알게된 사실인데 이러한 오류는 로그아웃 시에 쿠키세션을 초기화하지 않아서 발생할 수도 있다고 한다.

먼저 세션 관련된 것이기 때문에 세션을 제대로 초기화시키는지 확인을 했다.

http
  .formLogin((auth)->auth.loginPage("/login")
    .loginProcessingUrl("/login")
    .defaultSuccessUrl("/main", true)
    .failureHandler(new CustomAuthenticationFailureHandler())
    .permitAll()
    )
						
  .logout((logout) -> logout
    .logoutUrl("/logout") 
    .logoutSuccessUrl("/login")
    .invalidateHttpSession(true) 
    .clearAuthentication(true) 
    .permitAll()
    );

세션 초기화를 정상적으로 실행했다. 다시 재실행을 해도 같은 오류가 뜬다.

정보를 찾아보다가 혹시 몰라 크롬에서 쿠키를 삭제하고 재실행을 해봤다.

정상적으로 실행이 됐다

그럼 오류는 쿠키를 삭제하지 않아서 그런 것 같다는 생각을 하고 로그아웃 핸들러를 작성했다.

http
  .formLogin((auth)->auth.loginPage("/login")
    .loginProcessingUrl("/login")
    .defaultSuccessUrl("/main", true)
    .failureHandler(new CustomAuthenticationFailureHandler())
    .permitAll()
    )
						
  .logout((logout) -> logout
    .logoutUrl("/logout") 
    .logoutSuccessUrl("/login")
    .addLogoutHandler((LogoutHandler) new LogoutHandler() {
      @Override
      public void logout(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) {
        String cookieName = "JSESSIONID";
        Cookie cookie = new Cookie(cookieName, null);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(0);
        response.addCookie(cookie);
        }
    .invalidateHttpSession(true) 
    .clearAuthentication(true) 
    .permitAll()
    );

수정된 코드이다.

  1. 먼저 크롬에 들어가서 검사 -> 애플리케이션에서 쿠키를 확인했다.
  2. 쿠키 이름은 JSESSIONID, 따라서 속성이 null 이고 변수 이름이 JESSIONID 인 새로운 객체를 만들었다.
  3. 그리고 cookie.setMaxAge(0) 을 이용하여 유효 시간을 0으로 만들어 만료시켰다.
  4. 이후 클라이언트로 전송하여 문제가 해결되었다.

카테고리:

업데이트: