본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


Spring Security

스프링 시큐리티 공부 15 - WebExpressionVoter 와 PreinvocationAuthorizationAdviceVoter

반응형

 

기본적으로 권한에서 가장 중요한 역할을 하는 것은 AccessDicisionManager를 구현해서 처리할 수 있다. 하지만 기존에 잘 구현된 소스들을 활용할려면 Voter 기반의 AccessDecisionManager를 사용하는 것이 좋다 .  

 

 

Affirmativebased(하나만 통과 시켜도 통과)  ,

ConsensusBased(다수결 위원회) ,

UnanimouseBased(만장일치) 로 Voter의 정책을 정할 수 있지만

 

보통 Affirmativebased를 사용한다 . 

 

권한 심사에 참여하는 Vote의 종류에는 

 

FilterSecurityInterceptor 가  사용하는 WebExpressionVoter와 

 

GlobalMethodSecurityInterceptor에서 사용하는

RoleVoter,  AuthenticatedVoter ,  PreinvocationAuthorizationAdviceVoter가 있다 

 

RoleVoter,  AuthenticatedVoter  같은 경우는 대부분 PreinvocationAuthorizationAdviceVoter로 커버가 가능하기 떄문에 

PreinvocationAuthorizationAdviceVoter를 주로 사용한다. 

 

 

Voter 들에 대해 하나씩 살펴보자 

 

 

RoleVoter

 

Role 기반의 권한은 리눅스부터  아파치 , 톰켓등 IT 초기부터 전통적으로 구현해서 사용하던 가장 직관적인 권한 체꼐이다 . 하지만 ,IT 기술들이 발달함에 따라 ROLE을 기반으로 권한을 판단하기에 ,  상황이 너무 다양해져 ROLE 만으로 권한을 판단하기엔  부족한 부분이 잇다 때문에 ROLE을 확장한 Authority 기반의 권한 체계를 사용하고 있다 . 기존의 ROLE기반이 가지고 있는 직관적이고 계층적인 사용성을 그대로 사용할 수 있도록 해주기 위해 RoleVoter가 사용된다

 

 ROLE_USER: GrantedAuthority

 권한계층 선언 : RoleHierachyVoter 

 

 

AuthenticatedVoter 

 

인증을 받았다면 그 인증의 종류가 어떤 종류인지를 판단한다. 이제 막 인증을 받고 들어온 사용자와 RemeberMe토큰을 통해서 들어온 사용자와 익명 사용자를 구분하기 위해 쓰인다 . RememberMe 인증 사용자는 탈취된 가지고 들어온 사용자일 수 있기 때문에 필요한 경우 한번 더 인증을 요구할 수 있다 .

  특이한 점은 인증이 완료된 RemeberMe토큰 보유 사용자에게도 익명 사용자와 같은 재로그인을 요구하는데 이는 토큰이 탈취되었을 때  재로그인 전까지는 해당 토큰이 무력화 되지 않기 때문에  그 부분까지 생각하여 RemeberMe토큰도 재로그인을 요구하는 것이다 

FullyAuthenticated 한 토큰만이 AuthentictaedVoter에게 Pass 판정을 받아 낼 수 있다.

 

 

 

WebExpressionVoter 와 PreinvocationAuthorizationAdviceVoter 

 

 

위의 두 Voter들은 아래의  SpEL을 사용하는 Voter 들로 대체 할 수 있다 . 

 

SpEL을 사용하는 Voter에는 

 

WebExpressionVoter , PreinvocationAuthorizationAdviceVoter 가 있다 . 

 

 

SecurityExpressionRoot

 

 

각각의 Voter가 SpEl 기반을 동작하기 위해서는 스프링 applicationContext에서 빈에 대한 정보들을 가져와 파싱하고  핸들링해 줄 Handler가 필요하고  WebExpressionVoter에서는 SecurityExpressionHandler  ,

PIAAVoter의 경우  MethodSecurityExpressionHandler 가  그 역할을 한다 .  

 

MethodSecurityExpressionHandler 클래스를 살펴 보면 인터페이스로 구현 되어있으며  빈에서 가져온 정보를 파싱할 ExpressionParser를 가지고 있고 Security Expression들을 작업할 EvaluationContext를 갖고 있다 . 

 

 

 

 

각 handler의 default 구현체는

 

DefaultSecurityExpressionHandler와 DefaultMethodSecurityExpressionHandler가 있다 . 

 

DefaultSecurityExpressionHandler에서는 

 

Authentication에 대한 검증을 해주는   AuthenticationTrustResolver를 저장하는 필드와 

Role에 대한 prefix를 정의하는 String 타입의 필드를 갖고 있다. 

SecurityExpressionHandler는  두 필드를  기반으로 createSecurityExpressionRoot() 를 통해 SecurityExpressionRoot를 생성한다. 

 

 

DefaultSecurityExpressionHandler

 

 

WebSecurityExpressionHandler는 WebSecurityExpressionRoot를 

 MethodSecurityExpressionHandler는 MethodSecurityExpressionRoot를 만들어 내는데  

 

 

WebSecurityExpressionRoot의 경우 아래처럼  request 객체를 가져와서 request에 접근하여 작업을 수행하거나 

hasIpAddress 메서드를 통해 사용자 Ip를 가져와 작업을 수행할 수 있게 해준다 .

Root객체이기 때문에  El을 사용할 때 따로 #을 붙혀주지 않아도 된다. 

(요청에서의 PathVariable은 변수로 취급되어 #을 붙혀줘야하고 ,  Bean에 대해 작업이 필요하다면 @를 SpEl의 문법대로

붙혀줘야한다 )

 

WebSecurityExpressionRoot

 

 

 

 

간단한 테스트로  WebExpressionVoter 와 PreinvocationAuthorizationAdviceVoter이 어떻게 SpEl을 사용하여 동작하는지 알아보자 

 

먼저 WebExpressionVoter은 FilterInterceptor에서 사용하기 떄문에 마찬가지로 filter단에서 동작한다. 

 

SecurityConfig에서  HttpSecurity에  authorizeRequests() 메서드를 통해 매번 요청에 대한 권한이 체크된다 . 

 

mvcMatcher().access() 를 통해 요청에 대해 el 기반으로 권한체크가 가능하다 . 

access() 의 인자로 el 문자열(configAttribute)를 적으면 된다 . 

 

먼저 El 문법은 ApplicationContext에 등록된 bean 에 접근가능하니 권한체크를 담당할 클래스를 bean으로 만들어 보자 

 

 

@Component
public class NameCheck {

    public boolean check(String name){
        return name.equals("ugo");
    }

}

 

간단하게 pathVariable로 넘어오는 이름이  등록된 이름("ugo")과 다를 경우에 권한을 제한하는 NameCheck라는 클래스를 만들고 @Component를 붙혀줬다  

 

 

 

access 안에 El 문법을 보면 @nameCheck 빈에 접근하여  , 그 안에 check메서드를 사용하고 있다 .  PathVariable은 변수이기 때문에 #name으로 접근할 수 있다. 

 

 

해당 요청을 받을 컨트롤러를 만들고

 

 

 

 

restTemplate를 활용해서 동작하는지 테스트 해보자   PathVarable로 들어오는 name이 "ugo" 라면 hello ugo 가 출력되고 테스트가 통과 될 것이다 . 

 

 

같은 Expression이 PreinvocationAuthorizationAdviceVoter에서는 어떻게 동작하는지 알아보자 

 

@RestController
public class HomeController {


    @PreAuthorize("@nameCheck.check(#name)")
    @GetMapping("/greeting/{name}")
    public String greeting(@PathVariable String name){
        return "hello " +name+"!";
    }
}

 

이번에는 Expression을 컨트롤러에 @PreAuthorize를 통해 달아줬다 .  스프링 컨텍스트 안에서 권한체크가 이뤄지기 때문에  

 

PreinvocationAuthorizationAdviceVoter를 통해 권한이 체크될 것이다.  

 

현재 위에서  정의한  Filter단에서의 권한체크를 지우지 않았기 떄문에 WebExpressionVoter 와 PreinvocationAuthorizationAdviceVoter 둘다 권한 위원회에 참여할 것이다.  

 

디버깅을 통해 AbstractSecurityInterceptor의 attemptAuthorization에서 

 

decide()를 호출하는 시점에 브레이크 포인트를 두고 흐름을 살펴보자 

 

 

 

 

 

위원회가 열리고 configAttributes를 보면 첫번째로 configure에 access()안에 정의한  WebExpressionConfigAttribute 가 넘어 온 것을 확인할 수 있다 . 

 

 

WebExpressionVoter가 투표를 마치고 PreinvocationAuthorizationAdviceVoter가 투표를하는데 이때 configAttribues를 보면 메서드에 선언한 어노테이션의  El이 읽어와지는 것을 볼 수 있다  .

 

 

 

반응형