✍️ 개발 기록

[🎵MIML] 리다이렉트 지옥 탈출한 썰..😵‍💫 (with. SessionCreationPolicy)

ming412 2023. 10. 20. 17:34

문제 상황

팀 프로젝트를 진행하면서 소셜 로그인 부분을 맡았다. (OAuth 제공자는 Spotify였다.)

원하는 것은 두 가지였다.

1. 보호된 uri에 접근했을 때 스포티파이 로그인으로 리다이렉트

2. 스포티파이 로그인 성공 후 다시 보호된 uri으로 리다이렉트 (당연히 authentication을 들고..🥲)

 

하지만 당연한게 당연하지 않았다(?)

 

SecurityContextPersistenceFilter

스포티파이 로그인에 성공했음에도, 리다이렉트 되었을 때 authentication에 null이 담겨있었다. 😭

 

문제 상황

따라서 인증되지 않은 사용자로 판단하여 다시 로그인 페이지에 리다이렉트 하고,

로그인 후에는 original uri에 리다이렉트, 또 다시 로그인 페이지로 리다이렉트...

말그대로 리다이렉트 지옥에 빠져버렸다..🔥

 

함께 프로젝트를 진행하고 있는 팀원 중 한 분이 SecurityContextPersistenceFilter를 살펴보라고 말씀해 주셨고, 원인과 해결 방법을 찾을 수 있었다. (정말 감사합니다ㅜㅜ)

 

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter는 SecurityFilterChain의 필터 중 하나로, 이름 그대로 SecurityContext를 영속화하는 책임을 가진 필터이다. 쉽게 말해서, 사용자의 인증(authentication) 및 권한(authorization) 정보를 관리하고 보존하는 필터이다.

 

기본적으로 이 필터는 세션을 사용하여 SecurityContext를 관리함으로써 요청 간에 상태 정보를 유지하는데, 동작 과정은 아래와 같다.

SecurityContextPersistenceFilter

1. SecurityContextRepository(잘 보기..🔥)를 사용하여 이전 요청에서 저장된 SecurityContext를 가져오려고 시도한다. (복원)

2. SecurityContext(=`contextBeforeChainExecution`)가 로드되면 해당 요청의 사용자 인증 및 권한 검한 검사를 수행한다. (SecurityContextHolder를 통해 현재 스레드 내의 SecurityContext에 저장된 사용자 정보를 기반으로 수행된다.)

3. 사용자 인증 및 권한이 확인되면, 요청을 계속 진행한다. (해당 요청을 처리하는 다른 필터 및 핸들러를 호출한다.)

4. 요청 처리가 완료되면, SecurityContextRepository를 사용하여 현재 스레드의 SecurityContext(=`contextAfterChainExecution`)를 저장한다. (이를 통해 사용자의 인증 및 권한은 다음 요청을 위해 보존된다.)

5. 최종적으로 요청을 처리한 후, 서버는 HTTP 응답을 클라이언트에 반환한다.

 

➡️ SecurityContextPersistenceFilter를 사용하여 SecurityContext를 보존하면, 사용자의 로그인 상태와 보안 정보가 여러 요청 간에 유지되어, 사용자는 로그인 한 후에 동일한 상태 정보를 유지할 수 있게 된다.

 

🙋‍♀️ 세션(Session)이란?
웹 애플리케이션에서 상태 정보를 유지하고 관리하기 위한 매커니즘. 브라우저와 서버 간의 히스토리.
🙋‍♀️ SecurityContext란?
스프링 시큐리티에서 사용자의 보안 정보 및 인증 상태를 저장하는 객체. 사용자가 로그인하면 SecurityContext에 사용자의 인증 정보(authentication)와 권한 정보를 담아 SecurityContextHolder에 저장한다.

 

위처럼 동작한다면, 리다이렉트된 페이지에서 authentication을 잃어버리는 일은 발생하지 않았을텐데,

위처럼 동작하지 않은 이유가 무엇일까?

 

SessionCreationPolicy에 따른 SecurityContextRepository 선택

 

위 동작 과정 2번에서 SecurityContextRepository를 사용한다고 했다.

 

SecurityContextRepository으로 기본적으로 HttpSessionSecurityContextRepository를 사용하는데, `SessionCreationPolicy.STATELESS`를 설정하면 NullSecurityContextRepository를 사용한다.

 

NullSecurityContextRepository를 사용하면, 세션에 담긴 이전 SecurityContext를 사용하지 않고 빈 SecurityContext를 로드하게 된다.

 

❗️ 따라서 인증 정보가 요청 간 유지되지 않으므로 모든 요청에서 새로운 인증이 필요하다.

 

세션 생성 정책

 

디버깅

프로젝트에서 이를 확인하기 위해 SessionCreationPolicy를 다르게 설정해 디버깅을 해보았다.

 

`SessionCreationPolicy.STATELESS`를 설정한 경우 - 세션 사용 X

SecurityContextRepository로 NullSecurityContextRepository가 선택되었고, NullSecurityContextRepository의 loadContext() 메서드가 빈 SecurityContext를 반환하는 모습을 확인할 수 있다.

 

`SessionCreationPolicy.STATELESS`를 설정하지 않은 경우 - 세션 사용 O

SecurityContextRepository로 HttpSessionSecurityContextRepository가 선택되었고, HttpSessionSecurityContextRepository의 loadContext() 메서드가 세션에서 SecurityContext를 가져와서 반환하는 모습을 확인할 수 있다.

 

문제 해결

결국 `SessionCreationPolicy.STATELESS` 설정을 해제하여 문제를 해결할 수 있었다.

 

SecurityContextPersistenceFilter

스포티파이 로그인 성공 후, 사용자가 요청한 페이지로 리다이렉트 되었을 때에도 authentication을 유지하고 있는 모습이다. 🎉

 

참고 자료

SecurityContextPersistenceFilter 알아보기

[스프링 시큐리티] SecurityContext 객체 생성, 저장, 조회(SecurityContextPersistenceFilter)

Spring Security 개념 잡기

Spring Security Filters

[Spring Security] Filter란?

[Spring] JWT 사용 시 Spring Security Filter 흐름 분석