BackEnd/Spring Security

[Spring Security] SecurityContextPersistenceFilter

샤아이인 2022. 8. 27.

본 글은 Spring Security docs 와 여러 블로그 들을 참고하고, 공부하면서 요약하였습니다.

 

https://docs.spring.io/spring-security/reference/servlet/authentication/persistence.html#securitycontextpersistencefilter

 

Persisting Authentication :: Spring Security

The first time a user requests a protected resource, they are prompted for credentials. One of the most common ways to prompt for credentials is to redirect the user to a log in page. A summarized HTTP exchange for an unauthenticated user requesting a prot

docs.spring.io

 

1. SecurityContextPersistenceFilter

 

 

▶ SecurityContext 객체의 생성, 저장, 조회

 

  • 익명 사용자
    • 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder저장한다.
    • AnonymouseAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장한다.

 

  • 인증 시
    • AnonymouseAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장한다.
    • UsernamePasswordAuthenticationFilter에서 인증 성공 후 SecurityContextUsernamePasswordAuthentication 객체를 SecurityContext에 저장한다.
    • 인증이 최종 완료되면 SessionSecurityContext를 저장한다.

 

  • 인증 후
    • Session에서 SecurityContext 꺼내어 SecurityContextHolder에서 저장한다.
    • SecurityContext안에 Authentication 객체가 존재하면 계속 인증을 유지한다.

 

  • 최종 응답시 공통사항
    • SecurityContextHolder안의 SecurityContext를 제거해야한다.
    • SecurityContextHolder.clearContext() 메서드를 호출해 인증 정보를 초기화 한다.

 

▶ Flow

1. 사용자가 Request 요청


2. SecurityContextPersistenceFilter는 매 요청마다 수행된다.


3. SecurityContextPersistenceFilter는 내부적으로 HttpSecurityContextRepository가 로직을 수행한다.
→ HttpSecurityContextRepository는 SecurityContext 객체를 생성, 조회 하는 역할을 하는 클래스이다.


4-1. 인증 전

  1. 새로운 컨텍스트  생성(SecurityContextHolder) → 이 때는 SecurityContext 안의 Authentication은 null 이다
  2. 그 다음 필터로 이동한다(chain.doFIlter)
  3. 인증 필터(AuthFilter)가 인증을 처리 한다.
  4. 인증이 완료되면 인증객체(Authentication)생성 후 SecurityContext 객체안에 저장된다.
  5. 다음 필터 수행(chain.doFilter) 
  6. Client에게 응답하는 시점에서 Session에 SecurityContext저장
    • SecurityContextPerstenceFIlter가 Session에 SecurityContext를 저장한다.
  7. ThreadLocal에서 SecurityContext을 제거한다.
  8. 응답(Response)

 

4-2. 인증 후

  1. Session에서 SecurityContext가 있는지 확인 → 인증이 된 이후 여기에 존재한다.
  2. SecurityContext를 꺼내어 SecurityContextHolder에 집어넣는다.
  3. 다음 필터 수행(chain.doFilter)

 

인증 전, 후를 간략하게 살펴보면 다음과 같다.

SecurityContextPersistenceFIlter는 인증 전(좌측)인증 후(우측)으로 나뉜다.

 

인증 전에는 SecurityContext에서 꺼낼 인증객체가 없으며, SecurityContextHolder에  SecurityContext를 생성한 뒤 인증필터(AuthFilter)를 수행 후 생성된 인증객체(Authentication)을 SecurityContext에 저장하여 다음 로직을 수행한다.

 

인증 후에는 Session에서 SecurityContext를 꺼내 SecurityContextHolder에 집어넣은 뒤 다음 필터들을 수행하게 된다.

 

 

2. 디버깅 해보기

SecurityContextPersistenceFilter 의 내부 코드는 다음과 같습니다.

 

생성자를 통해서 HttpSessionSecurityContextRepository 구현체를 사용하고있다.

 

HttpSessionSecurityContextRepository는 SecurityContextRepository 인터페이스를 구현하고 있기 때문에

다음과 같은 메서드를 지원하더군요

 

2-1) 익명 사용자의 요청

맨 처음 "/"로 접속하면 다음과 같이 SecurityContextPersistenceFilter 내부에서 this.repo 를 통해 loadContext를 호출한다.

여기서 this.repo는 위에서 생성자로 초기화했던 HttpSessionSecurityContextRepository이다.

 

loadContext() 내부는 다음과 같다.

만약 Session에 SecurityContext 객체가 있으면 해당 SecurityContext를 가져오게 된다.

 

하지만 나는 맨 처음 접속한것이기 때문에 인증을 한적이 없다. 따라서 위 구간은 pass된다.

따라서 다음 구간이 실행된다.

context = this.generateNewContext();

이렇게 새로운 SecurityContext를 생성하고 반환하여 처음 호출한 SecurityContextPersistenceFilter까지 전달한다.

 

setContext를 통해 ThreadLocal에 저장이 된다.

 

다음 필터로는 AnonymousAuthenticationFilter로 이동하게 된다.

SecurityContext에 저장되있는 인증객체가 없기 때문에 AnonymousAuthenticationToken을 생성한다.

지금 사용중인 사용자가 익명 사용자인지? 인증된 사용자인지? 구분하기 위해서 익명 사용자도 토큰을 만들어주는 것 같다.

 

이후 다시 요청했던 SecurityContextPersistenceFilter로 돌아가 finally 구문을 실행한다.

여기서 clearContext()를 통해 SecurityContextHolder 내부의 모든 SecurityContext를 지우게된다.

 

2-2) 인증 시도하는 사용자일 경우

Form 로그인으로 요청을 보낼경우, 처음에는 당연히 Session에 SecurityContext가 없다.

따라서 다음과 같이 contextBeforeChainExecution 내부의 인증객체는 null인 상황이다.

try 이하부분을 코드로 보면 다음과 같다.

try {
    var10 = true;
    SecurityContextHolder.setContext(contextBeforeChainExecution);
    if (contextBeforeChainExecution.getAuthentication() == null) {
        this.logger.debug("Set SecurityContextHolder to empty SecurityContext");
    } else if (this.logger.isDebugEnabled()) {
        this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
    }

    chain.doFilter(holder.getRequest(), holder.getResponse());
    var10 = false;
}

chain.doFilter()를 통해 다음으로 호출되는 필터는 AbstractAuthenticationProcessingFilter 인데 여기서 인증처리를 한다.

 

인증에 성공하면 다음과 같이 DaoAuthenticationProvider#createSuccessAuthentication에서 인증객체를 반환한다.

 

인증 객체가 다시 AbstractAuthenticationProcessingFilter 로 반환되고, 인증객체를 successfulAuthentication로 전달한다.

 

이를 받은 successfulAuthentication()에서 SecurityContextHolder에 인증객체를 저장시킨다.

AbstractAuthenticationProcessingFilter에서 이렇게 인증에 성공하게 된다.

 

인증이 성공한 이후 HttpSessionSecurityContextRepository 의 saveContext()를 호출하여 Session에 저장한다.

요층이 끝날 시점에 Session에 인증된 사용자의 SecurityContext를 "SPRING_SECURITY_CONTEXT"를 key로 저장하고 있다.

Session에 저장이 완료되었다면, 동일하게 clearContext를 한후 반환한다.

 

해당 사용자는 Session에 자신의 SecurityContext가 저장된 상황이다.

 

2-3) 인증에 성공한 사용자의 요청

이번에도 이전과 동일하게 SecurityContextPersistenceFilter 으로 맨 처음 이동한다.

 

이전과 다르게 이번에는 Session에 저장되있는 정보가 있다.

이를 사용해서 요청을 수행하고, 이전과 동일하게 요청이 끝날 시점에 clearContext를 통해 ContextHolder를 비워준다.

 

▶ Sequence Diagram

 

참고

https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0

 

스프링 시큐리티 주요 아키텍처 이해

목차

catsbi.oopy.io

 

댓글