[Spring Security] 임시 권한 부여(RunAsManager)

WOOK JONG KIM·2022년 12월 6일
0

패캠_java&Spring

목록 보기
88/103
post-thumbnail

@Secured 기반으로 작업된 사이트에서 필요에 따라 임시권한을 줄 수 있도록 설계된 것이 RunAs 기능

특정 상황에서 특정 권한을 가진 사람은 특별히 임시권한

권한 체크가 복잡한 시스템일수록 특정 메소드에 진입하는데 여러가지 조건식이 붙을 수 있는데 이때 RunAsManager를 사용한다면 이런 조건식을 단순하게 만들어 줄 수 있다

FilterSecurityInterceptor, MethodSecurityInterceptor

public Object invoke(MethodInvocation mi) throws Throwable {
  InterceptorStatusToken token = super.beforeInvocation(mi);
	Object result;
	try {
		result = mi.proceed();
	}
	finally {
			super.finallyInvocation(token);
  }
	return super.afterInvocation(token, result);
}

Before Invocation이 호출되고 Access Denied Exception이 발생하지 않았다면 메서드를 Proceed
-> proceed에서 Exception 발생 여부와 상관없이 이후 finallyInvocation을 처리

AbstractSecurityInterceptor

super에 해당하는 AbstractSecurityInterceptor에서 의 코드

protected InterceptorStatusToken beforeInvocation(Object object) {
	
    ... // 앞단에서 권한 체크 진행

	// Attempt to run as a different user
	Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
	if (runAs != null) {
		SecurityContext origCtx = SecurityContextHolder.getContext();
		SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
		SecurityContextHolder.getContext().setAuthentication(runAs);
		// need to revert to token.Authenticated post-invocation
		return new InterceptorStatusToken(origCtx, true, attributes, object);
	}
	// no further work post-invocation
	return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

}

protected void finallyInvocation(InterceptorStatusToken token) {
	if (token != null && token.isContextHolderRefreshRequired()) {
		SecurityContextHolder.setContext(token.getSecurityContext());
	}
}

앞단에서 권한 체크를 진행 한 뒤 Run_As로 되어있는 attribute(String)가 있다면 이를 가지고 RunAS가 포함된 Authority가 있는 RunAsAuthenticationToken을 발급

이렇게 임시로 권한이 추가된 AuthenticationToken을 넘겨주면 이후 Security Context를 새로 만들어서 Context를 비운 다음 RunAs 토큰으로 채워서 넘겨줌

run AS 토큰이 들어오지 않았다면 iscontextHolderRefreshRequired가 false

RunAsManagerImpl

public class RunAsManagerImpl implements RunAsManager, InitializingBean {

  public Authentication buildRunAs(
		Authentication authentication,
		Object object,
		Collection<ConfigAttribute> attributes
  ) {
		List<GrantedAuthority> newAuthorities = new ArrayList<>();
		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {
				GrantedAuthority extraAuthority = new SimpleGrantedAuthority(
						getRolePrefix() + attribute.getAttribute());
				newAuthorities.add(extraAuthority);
			}
		}
		if (newAuthorities.size() == 0) {
			return null;
		}
		// Add existing authorities
		newAuthorities.addAll(authentication.getAuthorities());
		return new RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),
				newAuthorities, authentication.getClass());
	}
}

buildRunAS에서는 attribute Collection을 돌면서 RunAs로 시작하는지 검사

-> Run_AS라는 PREFIX가 있으면 그 PREFIX에 대해서 ROLE을 앞에 붙여서 GrantedAuthority의 리스트인 newAuthorities에 추가

ex)ROLE_RUN_AS_PRIMARY


코드 예시

MethodSecurityConfiguration

...

	@Override
    protected RunAsManager runAsManager() {
        RunAsManagerImpl runas = new RunAsManagerImpl();
        runas.setKey("runas");
        return runas;
    }
    
...

Controller

	@Secured({"ROLE_USER", "RUN_AS_PRIMARY"}) // Affirmative 기반의 Access Decision Manager이기에
    // AttemptAuthorize를 넘어가기 위해 "ROLE_USER"를 추가하였음
    @GetMapping("/allpapers")
    public List<Paper> allPapers(@AuthenticationPrincipal User user){
        return paperService.getAllPapers();
    }

PaperService

	@Secured({"ROLE_PRIMARY", "ROLE_RUN_AS_PRIMARY"})
    public List<Paper> getAllPapers() {
        return paperDB.values().stream().collect(Collectors.toList());
    }

테스트 코드

	@DisplayName("6. user1이 임시로 교장선생님 권한을 얻어서 모든 시험지를 가져온다")
    @Test
    void test_6() {
        paperService.setPaper(paper1);
        paperService.setPaper(paper2);
        paperService.setPaper(paper3);

        client = new TestRestTemplate("user1", "1111");
        ResponseEntity<List<Paper>> response = client.exchange(uri("/paper/allpapers"),
                HttpMethod.GET, null, new ParameterizedTypeReference<List<Paper>>() {
                });

        assertEquals(200, response.getStatusCodeValue());
        assertEquals(3, response.getBody().size());
        System.out.println(response.getBody());

    }
profile
Journey for Backend Developer

0개의 댓글