[Spring Security] 아키텍처 이해 - 인증 저장소 필터( SecurityContextPersistenceFilter )

식빵·2022년 8월 20일
0
post-thumbnail

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다.




🥝 SecurityContextPersistenceFilter


SecurityContext 객체를 생성, 저장, 조회를 하는 필터이다.
이 필터는 인증 안한 상태(익명사용자), 인증 시, 인증 후에 따른 동작을 달리한다.

이번 게시물에서는 SecurityContextPersistenceFilter 라는 명칭 대신 SCP-Filter 라고 부르겠습니다!
너무 길어서요 😅


1. 익명 사용자 접근

  • SCP-Filter 에서 SecurityContext 생성 ==> SecurityContextHolder 에 저장한다.

  • 이후에 익명 사용자에 대한 처리를 위한 AnonymouseAuthenticaitonFilter 에서 익명 사용자용 인증토큰(= AnonymouseAuthenticationToken)을 생성하여 SCP-Filter 에서 생성한 SecurityContext 에 저장한다.


2. 인증 성공 동작

  • SCP-Filter 에서 SecurityContext 생성 ==> SecurityContextHolder 에 저장한다.

  • UsernamePasswordAuthenticationFilter 에서 인증을 성공하면 인증 토큰(UsernamePasswordAuthentication)을 SecurityContext 에 저장한다.

  • 최종적으로 Session에도 SecurityContext 를 저장한다.


3. 인증 후의 동작

  • Session 에서 SecurityContext 를 꺼내서 SecurityContextHolder 에 저장한다.

  • SecurityContext 안에 Authentication 객체 존재 시, 인증 상태를 계속 유지하게 된다.


4. 공통적인 최종 동작

  • SecurityContextHolder.clearContext() 를 호출하여 SecurityContext 기존 데이터를 지운다.





🥝 프로세스 한눈에 보기


  • 참고: SCP-Filter 안에 있는 HttpSecurityContextRepositorySecurityContext 를 생성하고 조회한다.





🥝 프로세스 코드 추적


1. 익명 사용자 접근

1-1. SCP-Filter.doFilter

스프링 부트 애플리케이션을 실행시키면 SCP-Filter 에서 doFilter 메소드가 동작하고 이 과정에서 this.repo.loadContext(holder); 를 호출하여 HttpSecurityContextRepository 에서 SecurityContext 로딩을 시도한다.


1-2. HttpSecurityContextRepository.loadContext

여기서는 readSecurityContextFromSession 이라는 메소드명을 보면 알다시피, 현재 세션에 저장된 SecurityContext 가 있는지를 확인한다.

그래서 있으면 가져오고, 없으면 null 을 반환한다. 현재는 세션 자체가 null 이라서 이 메소드의 결과도 null 을 반환한다.

참고: this.springSecurityContextKey = "SPRING_SECURITY_CONTEXT" 이다.

이후에 null 이 return 되고 나면 다시 SCP-Filter.doFilter 메소드로 돌아온다.
그리고 나서 generateNewContext() 메소드를 호출하여 새로운 SecurityContext 생성을 시도한다.

인증 객체를 갖고 있지 않는 비어있는 SecurityContext를 생성 및 반환한다.

최종적으로 SCP-Filter.doFilter 에서는 계속해서 체이닝된 필터들을 호출하게 된다.



1-3. AnonymousAuthenticationFilter.doFilter

현재는 인증을 하지 않은 사용자가 자원에 접근하는 것이므로
AnonymousAuthenticationFilter 에 걸리게 된다.

이 필터에서는 위처럼 익명사용자 인증토큰을 생성하고,
SecurityContext 를 새로 또 생성한다.

그리고 SecurityContext 에 인증토큰을 세팅하고, 최종적으로
해당 SecurityContext 를 Holder 에 저장하고 다음 필터를 호출한다.

참고: 익명 사용자 필터를 거치게 되면 Session 에 SecurityContext 를 저장하는 과정을 거치지 않는다!



1-4. SecurityContext 기존 데이터 삭제

최종적으로 response 를 보내기 전에 기존 SecurityContext 정보를 지운다.





2. 인증 성공 시

위 익명 사용자 접근 시와 비슷한 게 많으므로 몇가지 과정은 설명을 생략 또는 요약한다.


2-1. SCP-Filter.doFilter

  • Session 에서 SecurityContext 유무 확인
  • 없으면 생성하고 Holder 저장
  • 다음 필터에 요청 위임

2-2. AbstractAuthenticationProcessingFilter.successfulAuthentication


2-3. SCP-Filter.doFilter ==> finally {...}

  • 일단 SecurityContext 객체를 미리 빼와서 contextAfterChainExecution 에 저장.

  • SecurityContextHolder.clearContext(); 를 호출하여 SecurityContextHolder 내부의 ThreadLocal 내에서 SecurityContext 를 지운다. 하지만 현재 contextAfterChainExecution 에 미리 저장했기 때문에 계속해서 SecurityContext에 접근이 가능하다. 이러는 이유는 이후 메소드에 SecurityContext 객체를 인자로 사용하기 위함이다.

  • this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
    이게 현재 세션에 SecurityContext 정보를 저장할지 말지를 결정한다.
    자세한 내용은 바로 아래에 있다.


2-4. HttpSessionSecurityContextRepository.saveContext

일단 현재 세션에 SecurityContext 정보가 있는지 뒤져본다.
그리고 이어서 내부에 있는 인증 객체를 꺼내서 null 인지, 익명 토큰인지를 알아본다.
만약 둘 중 하나라도 trueHttpSessionSecurityContext 를 저장하는
작업을 하지 않고 메소드가 return 된다.


반대로 SecurityContext 가 보관하는 인증객체가 인증에 성공한 인증 객체면 HttpSessionSecurityContext 를 저장하는 과정을 거치게 된다.





3. 인증 이후 동작

3-1. SCP-Filter.doFilter

  • this.repo.loadContext 메소드에 의해서 현재 세션에서 저장되어 있는 SecurityContext 를 읽어온다. 인증 이후면 SecurityContext 를 반환받는다.

  • 현재는 인증 이후이므로 당연히 SecurityContext 가 존재한다.

  • 꺼내온 SecurityContext 를 이제 SecurityContextHolder 에 저장해서 코드 내에서 전역적으로 사용할 수 있도록 한다.

  • 이후에 마찬가지로 다음 필터에게 요청을 위임한다.


3-2. SCP-Filter.doFilter ==> finally {...} ===> HttpSessionSecurityContextRepository.saveContext

이전과 달리 현재 SecurityContext 에 어떤 변화가 있는지를 확인한다.
변화가 없다면 세션 저장 과정을 거치지 않는다. 그냥 기존 세션에 있는 SecurityContext 를 계속 쓰겠다는 의미다.

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글