본문 바로가기

Spring Security Session 과 RestAPI 본문

Dev

Spring Security Session 과 RestAPI

겨울바람_ 2023. 8. 17. 11:46

이번글은 정보를 정리한 포스트가 아닌 에러를 해결하는 과정에서 새롭게 알게 된 사실을 정리한 글이다. 

 

기존에 진행하던 프로젝트들은 하나같이 JWT를 사용하여 인증을 진행했다. JWT를 사용한 인증 방식을 선택한 것에 이유를 대자면 여러가지 이유를 댈 수 있겠지만, 가장 큰 이유로는 최근의 Spring Security 관련된 블로그 포스팅 혹은 강의를 살펴보면 대부분이 JWT 방식을 채택했기 때문일 것이다. 

 

하지만, JWT 방식이 장점만을 가지고 있는 것은 아니기에 또 다른 인증 방식인 Session 방식 또한 시도해보기로 결정했다. 아주 예전부터 주식을 취미로 해오고 있던 나였기에 주식과 관련된 프로젝트를 진행해보고 싶었고, 마침 MSA 에 대해 간단하게 공부하며 RestTemplate 를 사용하는 방법 또한 얼추 알고 있었기에 Python 의 yfinance 라이브러리를 통해 받아온 실시간 주가 정보를 웹소켓 방식으로 Spring 으로 전달하는 실시간 주가 정보 조회 프로젝트를 진행하기로 했다.

이번 포스팅의 주된 내용은 해당 프로젝트에 관한 내용이 아니기에 진행 상황, 어떤 식으로 코드를 짰는지 등은 생략하겠다. 결과적으로 말하자면 반은 성공이고 반은 실패라고 할 수 있겠는데 RestTemplate 를 사용한 Client -> Spring -> Django 간의 요청은 성공했지만, 실시간 주가 정보를 웹소켓 방식으로 Django -> Spring -> Client 로 전달하는 것은 실패했다. 

 

사실 실패라기 보다는 코드를 짜려다가 굳이 한 번 서버를 더 거쳐서 전달할 필요가 있나, 라는 생각이 들어서 Client -> Django 간의 웹소켓 응답 방식으로 변환한 것이다. 결과적으로 원래 생각했던 것과는 조금은 다르지만 삐걱거리며 동작하는 Service 로직을 완성한 나는 기존의 생각대로 Session 방식의 인증을 적용하기 위해 Spring Security 를 적용했는데, 곧장 에러를 마주했다. 

 

해당 에러에 대해 설명하기에 앞서, Spring Security 의 Session 방식 인증을 사용하는 대부분의 블로그 포스팅은 Spring Security에 내장된 formLogin 을 활용하여 로그인을 진행했지만 나는 Client 로부터 Json 객체를 전달받아 로그인을 진행하는 RestAPI 방식을 차용했기 때문에 formLogin 을 사용하지 않았다.

 

Json 객체를 이용한 RestAPI 로그인 방식은 자주 해봤으니 큰 어려움 없이 코드를 작성할 수 있었고, Postman 으로 시험해본 결과 성공적으로 기능이 정상적으로 수행되는 것을 확인할 수 있었다. 

로그인은 성공적으로 동작했는데..

Postman 의 Cookie 를 확인해보니 Session 또한 잘 전달된 것을 확인할 수 있었기에 나는 곧장 다른 요청을 보냈는데 ... 

주식 정보는 어디가고 등장한 Forbidden 403
쏟아지는 에러 로그들..

Spring Security 에 설정해둔 customAuthenticationEntryPoint 의 printStackTrace 가 찍혀있었기에 이를 확인해보니 Full authentication is required to access this resource 에러가 발생해 있었다. 해당 에러에 대해서 구글링 해본 결과 Authentication 객체에서 무언가 문제가 발생한 것 같았다. Authentication 객체에 대한 log 를 설정한 뒤 다시 한 번 실행시켜본 결과 Authentication 객체의 값이 Null 로 설정되어 있었다.

AuthenticationEntryPoint 에서 getAuthentication() 을 출력해봤더니 null 값이 출력된다

분명 로그인은 성공적으로 완료되지 않았나..? 라는 의문이 들어 LoginSuccessHandler 에도 log 를 찍어서 Authentication 객체를 확인해보았고, Authentication 객체의 값이 잘 설정되어 있는 것을 확인할 수 있었다. 

LoginSuccessHandler 의 log Authentication 객체가 존재하는 것을 확인할 수 있다

차라리 로그인이 완료된 후의 Authentication 객체가 Null 값이었다면, 로그인 로직에서 무언가가 잘못된게 아닌가 하고 범위를 좁힐 수 있었겠지만 로그인 로직에는 아무런 문제가 없다는 것이 증명되었기에 더욱 혼란스러웠다. 

 

내 짧은 지식으로는 인증에 성공한 후 생성된 Authentication 객체는 ThreadLocal 에 할당된 SecurityContext 에 저장되는 것으로 알고 있었고, Spring Security 의 기본 인증 방식은 Session 방식이기에 따로 인증을 위한 필터를 커스터마이징 할 필요가 없는 것으로 알고 있었다. 

 

처음에는 Postman 으로 보낸 각기 다른 두 가지 요청을 다른 세션이라고 인식하는 것이라고 생각해서, Session 정보를 ThreadLocal 의 SecurityContext 가 아닌 In-Memory DB 인 Redis 에 저장하도록 변경한 뒤 다시 실행해봤지만 달라진 점은 없었다.

 

며칠동안 고민한 뒤 가까스로 원인을 발견했는데,

 

결과적으로 원인은 Session 인증 방식을 사용하면서도 formLogin 을 사용하지 않고 RestAPI 방식을 사용했기 때문이다. formLogin 을 사용할 경우 SecurityContextRepository 가 자동으로 등록된다. 

 

아래는 FormLoginConfigurer 의 부모 AbstractAuthenticationFilterConfigurer 가 Override 하고 있는 메소드이다. 

AbstractAuthenticationFilterConfigurer 의 메소드 중 일부

해당 부분을 조금 더 파고들어보면 SecurityContextRepository 가 Null 일 경우 HttpSessionSecurityContextRepository 를 설정해주는 것을 확인할 수 있다.

SecurityContextConfigurer 의 getSecurityContextRepository()

즉, formLogin 방식이 아닌 RestAPI 방식을 차용하게 되면서 SecurityContextRepository 에 대한 부분이 사라지게 되었고, STATELESS 방식이던 JWT 와 다르게 Session 을 HttpSession 에 저장하지 못해 에러가 발생하게 된 것이다. 

 

만들어두었던 커스텀 필터에 setSecurityContextRepository 를 사용해서 HttpSessionSecurityRepository 를 등록해주었고, 정상적으로 동작하는 것을 확인할 수 있었다.

AAPL 의 주식 정보가 잘 나오고 있다

참고
https://www.zodaland.com/tip/63
 

정말 힘들었는데 이 분이 쓴 블로그 포스팅 덕분에 해결할 수 있었다

'Dev' 카테고리의 다른 글

HTTP  (1) 2024.02.24
Transaction  (0) 2024.02.20
In-Memory NoSQL DBMS Redis  (0) 2023.06.18
JVM Runtime Data Area Structure  (1) 2023.05.08
JWT를 사용하여 웹 사이트 공격에 대비하기 (with.Spring Security)  (0) 2023.04.29
Comments