본문 바로가기

오류일기

Spring Security Session, Redis직렬화 문제, 근데 이제 Seriallizable 인터페이스를 곁들인

❗ 해당 포스팅은 사진과 내용이 많습니다!

 

상황

현재 진행하고 있는 프로젝트에서 Spring Security를 도입하였습니다. 인증및 인가 방식은 session방식을 선택하였고 session저장소는 In memory가 아닌 Redis에 저장하고자 하였습니다. 

In memory방식을 통해 구현을 하였을때는 정상적으로 동작하였지만 @EnableRedisHttpSession을 통해 Redis에 session을 저장하려하니 다음과 같은 에러가 발생하였습니다.

 

직렬화를 할 수 없다 나옵니다.

 

원인(추측)

인프런를 통해 Spring Security를 공부하였고 해당 강의에서는 @EnableRedisHttpSession을 통해 손쉽게 session 저장소를 변경하는 것으로 보아 저만 이러한 문제가 발생하는 듯 합니다...

 

ㅠㅠ..

 

하지만 에러가 터진다고 안할수는 없기에 문제의 원인을 생각해보면 In memory방식으로 인증및 인가를 진행하였을 당시는 문제없었던걸로 보아 session저장소를 Redis로 옮기는데 있어 문제가 발생한것으로 추측됩니다. 또한 직렬화를 할 수 없다는 에러문구로 보아 인증객체가 담겨있는 SecurityContext를 Redis에 저장하는 로직에 문제가 발생한 것을 추측됩니다.

 

원인및 해결방법(추측)

제가 추측한 원인이 맞다면 session 객체를 저장하고 불러오는 역할을 담당하는 SecurityContextHolderFilter에서 문제가 발생되었으리라 생각됩니다. 그렇기에 지금 당장 떠오르는 해결방법은 아래와 같습니다.

  1. SecurityContextHolderFilter를 직접 구현하여 필터를 갈아끼우는기
  2. SecurityContextHolderFilter속에서 Session을 저장하는 클래스를 찾아 구현하기

++ 추가

날 잡고 예외가 발생하는 이유에 대해 디버깅을 하며 확인해보았습니다.(아쉽게도 Spring Security + 사용자 정의 필터+ Session + Redis를 사용하는 환경에서 직렬화 문제가 발생하는 글에 대한 포스팅은 없었습니다.)

직렬화 문제가 발생하는 메소드를 확인하기 위해 세션을 저장하는 로직을 디버깅을 통해 따라가보았습니다.

 

직렬화 문제를 확인하며 알 수 있었던 것은 크게 두가지가 있습니다.

만약 필터에서 인증이 성공한다면 아래와 같은 순서로 프로세스가 진행됩니다.

  1. response 객체에 응답값을 작성합니다.
  2. 세션 영속화가 있다면 세션 영속화를 진행합니다.

response 객체에 응답값을 작성하는 모습

응답값에 값을 저장하고 JsonGenerator의 값을 초기화하는 모습니다.

outputBuffer에 제가 원하는 값을 json 형태로 만들어 Writer를 통해 작성해준 모습니다.

이후 해당 값을 response에 flush해주고 fush가 성공했는지 실패했는지 확인을 하며 response의 응답값을 작성하는 로직은 끝납니다.(flush해주었는지 boolean 형식으로 확인하는 코드가 있었지만 해당 코드에서 제가 찾는 예외가 발생하지 않아 넘어가 스크린 샷을  찍지는 못하였습니다...)

 

세션 영속화를 진행하는 모습(Redis에 저장하는 모습)

스프링 시큐리티는 아래의 writeObject 메서드를 이용해서 Redis에 값을 저장하고 있습니다.

 

해당 메소드에서 발생할 수 있는 예외의 종류를 주석으로 알려주고 있습니다. (문제 해결에 대한 힌트)

 

이후 세션 영속화를 시도하는 과정에서 예외가 발생하는 스크린 샷입니다.

objectOuputStream.writeObject를 실행시키면..
예외가 발생하여 catch문에 잡힙니다.

그리고 해당 코드에서는 SerializationFailedException 예외로 기존의 예외를 감싸서 자신을 호출한 메서드로 던집니다. 기존의 발생한 예외는 NotSerializableException입니다. 그리고  ObjectOuputStream의 writeObject메소드 주석을 보면  발생하는 예외와 이유를 친절하게 알려주고 있습니다.

 

이를 통해 한가지를 확인하면 예외가 발생한 이유는 다음과 같이 정의할 수 있습니다.

Redis에 SecurityContext타입의 객체를 저장하는 과정에서 해당 타입을 직렬화할 수 있는 클래스를 구현하지 않았기 때문 

 

해결 방법

ObjectOutPutStream 클래스의 writeObject메소드의 주석을 보면 “NotSerializableException”이 발생하면 Java.io.Seriallizable 인터페이스를 구현하라고 나와있습니다. 해결 방법을 알았으니 이제 이를 구현해봅시다!

 

저는 Seriallizable 인터페이스를 구현하는 특정 클래스를 생성하고 이를 security의 API에 추가하는 방식으로 구현해야 하는줄 알고 방법을 찾아보았으나... 제가 직렬화를 하고자하는 클래스에서 Seriallizable 인터페이스를 바로 구현하면 된다고 합니다..!

그리고 다음과 같이 코드를 추가하면..

 

Serializable 인터페이스를 구현한 모습

 

다음과 같이 200 OK와 함께 응답값이 성공적으로 오고...

 

성공!

 

Redis에서 값을 확인해보면...

 

세션이 올바르게 저장되어 있습니다.

 

후기

처음 문제를 겪었을때는 나한테 왜 그래... 라는 마음이었지만 디버깅을 하며 문제의 원인을 파악하고 해결방법을 찾아가는 과정이 재밌다고 느꼈으며 직렬화 문제 덕분에 스프링 시큐리티의 인증 과정을 직접 눈으로 확인해볼수 있었습니다! 또한 직렬화로 인해 호되게 당했기에 직렬화에 대해 좋든 싫든 공부를 하게되었고 이후 동일한 문제가 발생해도 쉽게 해결할 수 있을것 같다는 생각이 들었습니다!