BackEnd/Spring Security

[Spring Security] Authorization, FilterSecurityInterceptor

샤아이인 2022. 9. 3.

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

 

https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-requests.html

 

Authorize HttpServletRequest with FilterSecurityInterceptor :: Spring Security

By default, Spring Security’s authorization will require all requests to be authenticated. The explicit configuration looks like:

docs.spring.io

 

1. Authorization, FilterSecurityInterceptor

1-1) 인가 처리

인증 된 사용자가 특정 자원에 접근하고자 할 때, 접근 할 권한이 있는지를 증명하는 것이 인가(Authorization)이다.

 

  • 사용자가 특정 자원에 접근하고자 요청(Request)을 하면 그 사용자가 인증을 받았는지 확인한다.
  • 인증을 받은 사용자라면 해당 사용자의 자격(권한)이 해당 자원에 접근할 자격이 되는지 확인한다.
    • 위 그림에서 Web Application에서 Manager 권한으로 인증된 상태
    • Manager 권한이기 때문에 User, Manager Section까지는 접근이 가능하다.
    • Admin Section은 권한 부족으로 접근할수가 없다.

 

1-2) 스프링 시큐리티가 지원하는 권한 계층

▶ 1. 웹 계층

URL요청에 따른 메뉴 혹은 화면단위의 레벨 보안을 검사한다.

예를 들어 "/use" 리소스에 접근하려 할때 해당 자원에 대하여 사용자가 권한이 있는지를 확인한다.

 

▶ 2. 서비스 계층

화면 단위가 아닌 메소드 같은 기능 단위의 레벨 보안 검사.

 

▶ 3. 도메인 계층 

객체 단위의 레벨 보안, 객체(user)를 핸들링 하고자 할 때 도메인에 설정된 권한과 사용자가 가진 권한을 서로 심사해서 결정하는 계층

 

1-3) FilterSecurityInterceptor - 인가 처리 담당 필터

  • 마지막에 위치한 필터로써 인증된 사용자에 대해 특정 요청의 승인및 거부 를 최종적으로 결정
  • 인증객체 없이 보호자원에 접근을 시도하면 AuthenticationException 발생
  • 인증 후 자원에 접근 가능한 권한이 존재하지 않을 경우 AccessDeniedException 을 발생
  • 권한 제어 방식 중 HTTP 자원의 보안을 처리하는 필터 → URL방식으로 접근할 경우 동작한다.
  • 권한 처리를 AccessDecisionManager에게 위임한다.

 

▶ Flow

 

1. 사용자가 자원 접근(Request)


2. FilterSecurityInterceptor에서 요청을 받아서 인증여부를 확인한다.
    a. 인증객체를 가지고 있는지 확인한다.  
    b. 인증객체가 없으면(null) AuthenticationException 발생한다.
    c. ExceptionTranslationFilter에서 해당 예외를 받아서 다시 로그인 페이지로 이동하던가 후처리를 해준다.


3. 인증객체가 있을 경우 SecurityMetadataSource는 자원에 접근하기 위해 설정된 권한정보를 조회해서 전달해준다.
    a.권한 정보를 조회한다.
    b.권한정보가 없으면(null) 권한 심사를 하지 않고 자원 접근을 허용한다.

 

4. 권한 정보가 있을 경우 AccessDecisionManager 에게 권한 정보를 전달하여 위임한다.
    → AccessDecisionManager는 최종 심의 결정자다.

 

5. AccessDecisionManager가 내부적으로 AccessDecisionVoter(심의자)를 통해서 심의 요청을 한다.


6. 반환된 승인/거부 결과를 가지고 사용자가 해당 자원에 접근이 가능한지 판단한다.
    a. 접근이 거부되었을 경우 AccessDeniedException이 발생한다.
    b. ExceptionTranslationFilter에서 해당 예외를 받아서 다시 로그인 페이지로 이동하던가 후처리를 해준다.

 

7. 접근이 승인되었을 경우 자원 접근이 허용된다.

 

2. 디버깅 해보기

우선 SecurityConfig에 다음과 같이 설정하였다.

 

처음 "/"으로 요청을 보내면 FilterChainProxy를 통해 필터가 등록된것을 확인할 수 있다.

 

우선 ExceptionTranslationFilter를 살펴보자.

doFilter 안에서 다음 Filter를 호출하고 있을 뿐 이다.

다만, try - catch 구문을 사용했기 때문에 그다음 호출되는 필터에서 발생한 예외들이 이곳에서 catch됨을 알 수 있다.

 

따라서 다음으로 FilterSecurityInterceptor의 doFilter로 넘어온다.

 

호출되는 invoke()는 다음과 같다.

invoke 안에서 super.beforeInvocation() 즉, 부모의 메서드를 호출한다.

 

해당 메서드 내부에서는 obtainSeucurityMetadataSource()가 있는데, 여기서 해당 자원에 접근할 수 있는 권한정보를 가져온다.

현재 우리는 "/" 로 접근했기 때문에 따로 권한이 필요하지 않다.

따라서 "permitAll"에 해당되는 자원에 접근에 대한 권한정보를 받게 되었다.

위에서 Config 파일에서 permitAll()로 설정해줬던 부분이다.

 

이후 인증객체(Authentication)이 있는지를 확인한다.

만약 인증객체가 null이라면 예외를 발생시킨다.

우리의 경우 아직 로그인을 하지는 않았지만, AnonymousAuthentication을 통해 다음으로 넘어갈 수 있다.

 

이후 atteptAuthorization()을 통해서 실재로 인가처리를 위임시키고 있다.

내부의 accessDecisionManager를 통해 인가처리를 위임한다.

 

decide는 AffirmativeBased 내부에 있으며, 여기서 voter를 찾아와 검증하게 된다.

voter.vote()를 통해 반환받은 정수값 result를 통해 결과를 판단한다.

우리의 상황은 루트의 경우 자원 접근이 가능하기 때문에 result에 정수값 1이 담기며, 해당 페이지에 접근이 된다.

 

이번에는 "ROLE_USER"권한이 필요한 "/user" 로 접속해보자.

이번에는 이전과 달리 해당 리소스에 접근하기 위해서는 "ROLE_USER" 권한이 필요하다.

 

하지만 우리는 익명사용자 이다. 권한이 없다.

따라서 다음과 같이 voter에서 -1을 반환하게 되고, 결국 deny 된다.

 

따라서 다음과 같이 예외를 던지게 된다.

이렇게 발생한 예외는 위에서 말했듯 ExceptionTranslationFilter로 넘어온다.

다음 메서드로 넘어온다.

여기서 예외를 처리하고, 익명사용자 이기 때문에 "/login"페이지로 리다이렉션 시킨다.

 

로그인 페이지로 이동 한 후, 다시 로그인을 해보면, 이전과 동일하게 동작하다 권한 검증 부분에서 권한이 없어 403을 발생시킨다.

 

만약 사용자에게 "ROLE_USER"권한이 있었다면 성공했을 것 이다.

 

3. Sequence Diagram

 

4. 출처

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

 

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

목차

catsbi.oopy.io

 

댓글