기본적으로 권한에서 가장 중요한 역할을 하는 것은 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를 생성한다.
WebSecurityExpressionHandler는 WebSecurityExpressionRoot를
MethodSecurityExpressionHandler는 MethodSecurityExpressionRoot를 만들어 내는데
WebSecurityExpressionRoot의 경우 아래처럼 request 객체를 가져와서 request에 접근하여 작업을 수행하거나
hasIpAddress 메서드를 통해 사용자 Ip를 가져와 작업을 수행할 수 있게 해준다 .
Root객체이기 때문에 El을 사용할 때 따로 #을 붙혀주지 않아도 된다.
(요청에서의 PathVariable은 변수로 취급되어 #을 붙혀줘야하고 , Bean에 대해 작업이 필요하다면 @를 SpEl의 문법대로
붙혀줘야한다 )
간단한 테스트로 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이 읽어와지는 것을 볼 수 있다 .
'Spring Security' 카테고리의 다른 글
스프링 시큐리티 공부 19 - JWT 을 이용한 로그인 (3) | 2021.07.29 |
---|---|
스프링 시큐리티 공부 18 - JWT (Json Web Token) (0) | 2021.07.28 |
스프링시큐리티 공부 15 - 권한이란? (0) | 2021.07.08 |
스프링시큐리티 공부 14 - FilterSecurityInterceptor 와 ExceptionTranslationFilter (0) | 2021.07.07 |
스프링시큐리티 공부 13 - 세션관리 (세션 모니터링) (0) | 2021.07.06 |