[LG CNS AM Inspire Camp 1기] SpringSecurity (3) - Cookie vs Session 그리고 로그아웃 기능 (SecurityContextHolder)

정성엽·2025년 2월 9일
1

LG CNS AM Inspire 1기

목록 보기
45/53

INTRO

이번 포스팅에서는 웹 애플리케이션에서 인증된 사용자를 식별하는 방법 중 하나인 쿠키(Cookie)와 세션(Session) 에 대해 정리해보려고 한다.

HTTP는 기본적으로 Stateless(상태를 저장하지 않는 프로토콜) 이다.

즉, 사용자가 로그인한 후에도 서버는 해당 사용자의 상태를 기억하지 못한다.

그렇다면, 사용자가 로그인한 상태를 유지하려면 어떻게 해야 할까?

바로 쿠키와 세션을 활용하면 사용자의 인증 정보를 유지할 수 있다.

이번 포스팅에서는 쿠키와 세션의 개념 & 차이점에 대해서 정리해보려고 한다 👀


1. Cookie란?

쿠키는 클라이언트(브라우저)에서 저장되는 작은 데이터 조각으로, 웹 서버가 클라이언트에게 특정 정보를 저장하도록 지시할 수 있다.

이후 동일한 서버에 요청을 보낼 때 자동으로 해당 정보를 포함하여 보낸다.

여기서 우리는 쿠키가 브라우저에서 관리되는 내용이라는 것을 기억해야 한다.

그렇다면 쿠키는 어떻게 생성되고 어떻게 동작할까?

쿠키는 우선 사용자가 로그인하면, 서버는 인증을 수행한 후 Set-Cookie 헤더 를 통해 클라이언트에 특정 정보를 전달한다.

이후 클라이언트(브라우저)는 이 정보를 저장하고, 이후 동일한 서버로 요청할 때 자동으로 Cookie 헤더에 브라우저에 저장된 쿠키를 포함하여 보낸다.

💡 쿠키는 어디에 저장될까?

쿠키는 브라우저 메모리 혹은 브라우저가 사용하는 특정 공간에 저장 된다.

브라우저에서 개발자도구를 통해 저장된 쿠키 내용을 확인할 수 있다.

개발자도구의 Application으로 접근하면 스토리지 목록에 있는 Cookie 탭에 접근하여 쿠키를 확인할 수 있다.

💡 쿠키의 보안 문제

사실 쿠키를 사용한 인증 방식에는 문제가 있다.

이전에도 설명했지만, 쿠키는 브라우저가 생성하고 관리한다.

따라서, 사용자가 쉽게 접근할 수 있고 유출 및 위변조가 가능하다는 큰 문제점이 존재한다.

결국 쿠키를 사용한다면 중요 정보를 포함시키면 안되며, 쿠키만을 이용한 사용자 재인증은 보안 문제를 야기할 수 있음을 기억하자!


2. Session이란?

세션(Session)은 쿠키의 보안 문제를 해결하기 위해 탄생한 개념이다.

쿠키와의 가장 큰 차이점은 쿠키는 클라이언트가 관리하는 반면, 세션은 서버에서 관리된다는 것이다.

💡 Session 동작 과정

그렇다면 Session은 어떻게 동작할까?

Session 동작 과정

  1. 사용자가 로그인 요청을 보낸다.
  1. 서버는 사용자를 인증한 후, 서버 측에 사용자 정보를 저장하고 세션 ID(Session ID)를 생성한다.
  1. 서버는 세션 ID만을 쿠키에 담아 클라이언트에게 전달한다.
  1. 이후 클라이언트가 요청을 보낼 때 쿠키를 통해 세션 ID를 서버에 전송한다.
  1. 서버는 세션 ID를 기반으로 저장된 사용자 정보를 조회하여 적절한 응답을 제공한다.

실제로 이전에 살펴봤던 쿠키 탭에서 클라이언트에게 전달된 쿠키 Value를 살펴보면 다음과 같다.

이름을 보면 JSESSSIONID 이다. 즉, 쿠키에 저장된 값은 세션 아이디임을 쉽게 추론할 수 있다!


그렇다면 Cookie를 안전하게 사용하기 위해서는 어떻게 해야할까?

쿠키를 안전하게 운용하는 방법

  1. 쿠키에 중요 정보를 포함하지 않는다.
  1. 만약 중요 정보가 포함되어야 한다면? → 중요 정보가 포함될 경우에는 암호화해서 전달한다.
  1. 보안 속성을 활성화 ⇒ Secure 속성 ⇒ Https 통신을 할 때만 쿠키를 전달하도록 제한한다.
  1. 유효시간 (Expires)와 지속시간(Max-Age)를 최소한으로 설정한다.
  1. HttpOnly 속성을 활성화한다. → 브라우저에서 스크립트 등을 이용한 쿠키 접근을 제한

위 정리된 내용은 개념적인 내용이기 떄문에, 간단하게 읽고 넘어가도 괜찮을 것 같다.

다만, HttpOnly 속성에 대해서는 기억해둘 필요가 있다.

쿠키는 브라우저에서 관리된다고 이전에 설명한 바 있다.

따라서, 스크립트를 통해 쿠키를 접근하는 것은 별로 바람직하지 않은 방법이다.

이처럼 스크립트 코드를 통한 접근을 제한하기 위해 HttpOnly 속성을 사용할 수 있음을 기억하자!


4. Session 안전 운용

그렇다면 Session을 안전하게 사용하기 위해서는 어떤 부분을 고려해야 할까?

세션 ID 추측 취약점 방어

  1. 취약점
  • 세션 ID 생성 규칙을 유추해 앞으로 생성될 세션 ID를 미리 설정해서 사용할 수 있다.

  1. 해결 방안
  • 세션 ID 생성시, 생성 규칙을 유추할 수 없도록 해야 한다.

세션 ID 고정 취약점 방어

  1. 취약점
  • 인증 전후에 동일한 세션 ID를 유지하는 것

  1. 해결 방안
  • 인증 후 세션 ID를 재발행

세션 ID 탈취 방어

  1. 취약점
  • XSS(크로스 사이트 스크립트) 공격을 통해서 브라우저에 저장된 세션 ID를 탈취

  1. 해결 방안
  • XSS 공격을 방어 & HttpOnly 속성을 활성화하여 스크립트 코드 사용 제한

위 부분도 이론적인 내용이다.

하지만, 어떤 방법을 사용해서 안전하게 코드를 작성할 수 있는지는 대략적으로 파악하는게 좋을 것 같다.

💡 Session 만료 시간 설정

SpringBoot에서는 Session의 만료 시간을 설정할 수 있다.

사진과 같이 application.properties 파일에 session의 timeout을 지정해주는 방식으로 간단하게 설정할 수 있다.

💡 Session 고정 방어

세션 고정 취약점을 방어하기 위해서는 인증 후 새로운 세션 ID를 생성하여 반환하면 된다고 위에서 설명했다.

이 부분은 코드로 작성하여 해결할 수 있다.

우선 코드를 먼저 살펴보자

Sample Code

// newSession() : 새 세션을 생성하고 기존 세션은 무효화한다. (기존 세션 속성은 복사되지 않는다.)
// migrateSession() : 새 세션을 생성하고 기존 세션의 속성을 새 세션으로 복사 (기존 세션은 무효화)
// changeSessionId() : 기본값. 현재 세션 ID를 새로운 세션 ID로 변경 (기존 세션 속성은 그대로 유지)
// none() : 세션 고정을 방어할 수 없음 (권장 X)
http.sessionManagement(auth -> auth
        .sessionFixation(ses -> ses.newSession())
);

HttpSecurity 객체를 통해 .sessionManagement() 메서드를 호출하여 설정할 수 있다.

해당 코드도 이전 포스팅과 마찬가지로 SecurityFilterChain 빈 내부에서 작성하면 된다.

💡 다중 로그인 방어

세션을 통해 다중 로그인을 막거나 관련 규칙을 설정할 수 있다

우선 코드를 먼저 살펴보자

Sample Code

// maximumSessions : 하나의 아이디에 대해 다중 로그인 허용 개수
// maxSessionPreventsLogin(boolean) : 다중 로그인 개수를 초과한 경우 처리 방법
// - true : 초과시 새로운 로그인을 차단
// - false : 초과시 기존 세션 하나를 삭제
http.sessionManagement(auth -> auth
        .sessionFixation(ses -> ses.newSession())
        .maximumSessions(1) 
        .maxSessionsPreventsLogin(true)
);

.maximumSessions() 메서드를 통해 세션을 유지할 수 있는 사용자의 수를 지정할 수 있다.

즉, 다른 브라우저에서 2명이 동일한 아이디를 사용하면 .maxSessionsPreventsLogin() 메서드에서 정의된 내용으로 처리된다.
(새로운 로그인 차단 vs 기존 세션 삭제)


5. 로그아웃 기능 구현

로그아웃 기능을 별도의 포스팅을 빼기에는 분량 조절에 실패할 것 같아 같이 정리하려고 한다.

우선 코드를 살펴보자

Sample Code

public class LogoutController {
    @GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("[LOGOUT] : " + request + "||" + response);
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(request, response, authentication);
        }
        return "redirect:/home";
    }
}

우선 필자는 /logout URL로 요청이 들어오면 현재 로그인된 사용자를 로그아웃 시키려고 한다.

코드를 보면 SecurityContextHolder에서 Authentication 객체를 추출하여 가져온다.

다음으로 SpringSecurity에서 제공하는 SecurityContextLogoutHandler의 logout 메서드를 호출 할 때, 이 객체를 넘겨주는 방식으로 로그아웃을 처리할 수 있다.

여기서 SecurityContextHolder는 뭘까?

💡 SecurityContextHolder

이전 포스팅에서 UserDetails & UserDetailsService 인터페이스를 정리하는 부분에서 다음과 같이 정리했다.

이전 포스팅에서 UserDeatilsService 설명
조회된 사용자 정보(UserDetails)와 입력된 패스워드를 비교 후 인증 성공 시 SecurityContextHolder에 저장

또한, 세션에 로그인 정보를 저장하여 개발하는 과정에서 자유롭게 꺼내 사용할 수 있다고 언급한 바 있다.

Spring Security에서 현재 인증된 사용자 정보(Authentication 객체)를 저장하는 곳이 바로 SecurityContext이며, 해당 컨텍스트를 저장하는 곳이 SecurityContextHolder이다.

우리는 이 객체를 통해 현재 로그인한 사용자의 인증 정보를 어디서든 가져올 수 있는 것이다!

그렇다면 SecurityContextHolder는 어떻게 동작할까?

SecurityContextHolder 객체

  • 코드를 타고 들어가보자

우선 사진과 같이 기본적으로 MODE_THREADLOCAL 이라는 이름의 전략을 사용한다.

해당 이름을 사용하면 ThreadLocalSecurityContextHolderStrategy 타입의 인스턴스를 생성하여 전략으로 세팅한다.

해당 전략 객체를 타고 들어가면 내부적으로 ThreadLocal 로 contextHolder 필드를 선언하고 관리한다.

즉, SecurityContextHolder는 기본적으로 ThreadLocal을 활용하여 SecurityContext를 저장한다.

그렇다면 세션(Session)과는 어떤 관계가 있을까?

Session과 SecurityContextHolder
기본적으로 Spring Security는 SecurityContextPersistenceFilter 를 통해 SecurityContext를 세션(HttpSession)에 저장하고 유지한다.

따라서, 로그인 과정을 다시 정리하면 다음과 같다.

로그인 과정 (Update Version)

  1. 사용자가 로그인 폼을 통해 로그인 요청을 보냄
  1. UsernamePasswordAuthenticationFilter가 요청을 가로채서 AuthenticationManager에 인증을 요청
  1. AuthenticationManager가 UserDetailsService를 호출하여 사용자 정보를 조회
  1. 조회된 사용자 정보(UserDetails)와 입력된 패스워드를 비교하여 인증이 성공하면, Authentication 객체를 SecurityContext에 저장
  1. SecurityContextPersistenceFilter가 SecurityContext를 세션(HttpSession)에 저장
  1. 이후 요청이 들어오면 세션에서 SecurityContext를 불러와 SecurityContextHolder에 저장

즉, SecurityContextHolder 자체가 세션을 사용하지는 않지만,
Spring Security의 동작 방식에 의해 SecurityContext를 세션에 저장하고 유지하는 방식으로 동작한다!


OUTRO

이번 포스팅을 통해서는 간단하게 Cookie, Session과 관련된 내용을 정리했다.

다음으로 로그인 과정에서 Session을 사용하여 사용자 정보를 가져올 수 있다고 이전에 언급한 내용을 조금 더 자세히 정리해봤다.

사용자 정보는 Authentication 객체에 저장되며, 이는 SecurityContextHolder → SecurityContext → Authentication 구조로 정보가 저장되어있음을 기억하자 👊


📖 참고
Spring Security에서 SecurityContextHolder : 개념, 동작 방식, 그리고 사용법

profile
코린이

0개의 댓글

관련 채용 정보