[가자맵] spring-session-jdbc 익명 세션 생성 방지

김상인·2023년 8월 31일
0

가자맵

목록 보기
3/8
post-thumbnail

톰캣에서 기본적으로 메모리에 세션을 저장하고 관리하기 때문에 서버를 재실행하면 사용자의 세션이 사라져 재인증을 받아야 하는 상황이 생긴다.
그래서 보통 세션을 In-memory DB와 Disk based DB 중 하나를 선택해서 저장을 한다.

로컬에서는 MySQL을 사용하고 있었기 때문에 빠르게 세팅할 수 있는 JDBC를 선택하였다.
물론 운영할 때는 redis로 변경하는 것이 속도 측면에서 좋아 보인다.

implementation 'org.springframework.session:spring-session-jdbc'

위 라이브러리를 의존받으면 정말 간단하게 @EnableJdbcHttpSession 어노테이션으로 쉽게 세션을 DB에 자동으로 관리하고 저장할 수 있게 된다.

자세한 설정은 아래 사이트를 참고하자.
https://docs.spring.io/spring-session/reference/guides/boot-jdbc.html#httpsession-jdbc-boot-spring-configuration

세션 생성 흐름

톰캣에서는 org.apache.catalina.session 패키지의 ManagerBase.class 에서 세션을 생성한다고 한다.
spring-session-jdbc에서는 SessionRepositoryFilter 라는 클래스를 Servlet Filter Chain에 등록하는데 세션을 톰캣보다 먼저 처리하기 위해 다음과 같이

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;

처리 우선 순위를 가장 높게 줘서 필터를 등록한 걸 볼 수 있다.
그리고 SessionRepositoryFilterSecurityContextHolder 와 연관되어 있다.
SecurityContextHolder 는 ThreadLocal에서 인증 객체를 관리해 주는 용도이다.

이게 왜 연관성이 있냐면 공식 문서에서도 설명이 나와 있는데 spring-session-jdbcSecurityContextPersistenceFilter는 사용자 인증 정보를 HttpSession에 저장할 때 이 정보가 DB까지 영속화된다고 나와 있다.

그러면 세션이 어떻게 저장되는지 클라이언트의 요청과 응답을 통해 간단하게 알아보도록 하자.

응답

만약 로그인하려는 사용자가 API를 요청했다고 하자.

  1. 사용자 정보를 확인하고 인증된 사용자의 Authentication 구현체를 SecurityContextHolder에 저장한다.
  2. 응답을 주기 위해 거쳐왔던 Filter 중 Security Filter Chain에 속한 SecurityContextPersistenceFilter(세션 구현체는 HttpSessionSecurityContextRepository) 라는 필터에서 SecurityContextHolder에 저장된 Contenxt를 받아 세션에 사용자 정보를 저장한다.
    그리고 저장된 Context는 모두 비워준다.
  3. Servlet Filter Chain에 속한 SessionRepositoryFilter 에서 저장된 세션 정보를 DB에 저장한다.

다음과 같은 단계로 세션이 저장되는 것을 알 수 있다.

요청

사용자가 API를 요청했다고 하자.

  1. SessionRepositoryFilter 에서 세션이 있다면 SESSION_ATTRIBUTES 테이블에 사용자 정보까지 조회한다. 세션 속성에 그 정보를 저장하고 비즈니스 로직을 모두 수행한 후 응답 되기 전에 세션 정보를 DB에 저장한다.
    만약 세션이 없다면 익명의 세션을 생성하고 비즈니스 로직을 모두 수행 후 DB에 저장한다.
  2. SecurityContextPersistenceFilter 에서 세션이 존재하다면 세션 속성에서 사용자 정보를 추출하여 SecurityContextHolder에 저장한다.

위와 같은 단계로 세션을 처리하는데 이후 컨트롤러에서 인증된 사용자는 SecurityContextHolder에 Context가 존재하니 @AuthenticationPrincipal 어노테이션을 사용하여 인증 객체를 매개변수로 받을 수 있게 된다.

이슈 해결

요청 흐름 1단계를 보면 쿠키에 "SESSION"이라는 키 값이 없다면 새로운 세션을 생성한다고 하였다.
만약 세션이 없는 사용자가 API를 요청하면
위 이미지처럼 DB에 익명 세션이 생성된다.
DB에 무분별한 익명 세션 저장을 막기 위해 다음과 같이 해결하였다.

OncePerRequestFilter 를 상속받은 커스텀 필터 클래스를 만들고 Security Filter Chain에 속한 AnonymousAuthenticationFilter 이전에 필터를 등록한다.
커스텀 필터를 저 필터 이전에 등록한 이유는 저기서 인증 객체를 만들어주기 때문에 필터를 모두 순회하고 돌아올 때 세션을 조회하면 익명의 세션을 받을 수 있기 때문이다.

그래서 익명 세션을 무효화 시켜준다면 SessionRepositoryFilter 에서 세션 정보를 가지고 DB에 저장하는데 세션이 무효화되었기 때문에 DB에 저장이 안 된다.

그렇다면 익명 세션이라는 것을 어떻게 알 수 있을까?

디버깅해서 확인해 본 결과 익명 객체에 대한 세션의 속성 키값에는 "SPRING_SECURITY_SAVED_REQUEST" 가 저장돼있고
인증 객체에 대한 세션의 속성 키 값은 "SPRING_SECURITY_CONTEXT" 로 저장되어 있다.
그래서 위와 같은 코드로 익명의 객체를 판단할 수 있게 된다.

참조

https://semtax.tistory.com/92
https://yousrain.tistory.com/13
https://okky.kr/questions/259809
http://arahansa.github.io/docs_spring/session.html#api-session
https://docs.spring.io/spring-session/reference/guides/boot-jdbc.html

profile
백엔드 희망자

0개의 댓글

관련 채용 정보