[Spring Security] Spring Security Basic - AccessDecisionManager
들어가며
Spring Security에서 인증이 어떻게 발생하는지는 이전 포스트들에서 알아보았다.
인가(Authority)는 어디서 처리를 하고 있는지에 대해서 알아 본다.
AccessDecisionManager
public interface AccessDecisionManager {
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
AccessDeccisionManager는 3개의 메소드를 가진 인터페이스이며,dicide에서 인가에 대해서 판단 한다.supports는 Voter들이 Vote에 참여할 수 있는지 없는지를 결정하는데 사용한다.- 해당 인터페이스를 통해서 구현된 Authority 를 처리하는 class들은 3가지가 존재한다.
- AffirmativeBased : 여러 Voter중에 하나라도 grant라면 허용 (Default)
- ConsensusBased : 여러 Voter들의 의견 중 grant, deny의 값이 큰걸로 판단
- UnanimousBased : 여러 Voter들의 의견이 전부 grant인 경우 허용
AccessDecisionManager의 구현체들은List<AccessDecisionVoter>를 갖고 있어,AccessDecisionVoter가 vote를 실시 한다.
AffirmativeBased
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
AffirmativeBased.devide부분에서voter.vote의 reulst가ACCESS_GRANTED인 경우 error를 발생시키지 않고 넘어 가게 되어 있다.
AccessDecisionVoter
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
AccessDecisionVoter는vote에서 grant, abstain, denied 값을 반환 한다.- default는 abstain으로 설정되어 있고, grant, denied, abstain 값을 전달하게 되어 있다.
AccessDecisionVoter의 기본 구현체는WebExpressionVoter가 생성이 된다.- 기본 구현체는 아마 조건에 따라 다르게 생성되는데, form 방식에서는
WebExpressionVoter가 생성 된다.
AccessDecisionManager은 어디서 호출되는가?
AccessDecisionManager가 어디서 호출이 되어서 Authority를 판단하는지에 대해서 알아 본다.
FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
FilterChainProxy에서SecurityFilterChain을 가져오고,SecurityFilterChain의 많은 Filters 중에 마지막에FilterSecurityInterceptor가 존재한다.- 마지막 Filter답게,
chain.doFilter를 호출하는 부분이 없다. FilterSecurityInterceptor를 통해서 Authority를 검증하게 되는데, 실제 로직은AbstractSecurityInterceptor에서 구현되어 있다.
AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
protected InterceptorStatusToken beforeInvocation(Object object) {
...
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
...
}
}
AbstractSecurityInterceptor에서beforeInvocation을 할 때accessDecisionManager.decide을 호출하여 Voter들에게 vote를 하고, 에러가 발생하는지 안하는지로 Authority를 판단하게 되어 있다.AccessDeniedException가 발생한 경우AccessDeniedHandler에서 받아서 에러 핸들링을 이어 갈 수 있다.
마치며
- form 방식과 token 방식이 서로 다르게 구현되어 있는데, form 방식에 대해서 알아 보았다.
- 정말 간단하게 어떻게 호출이 발생되는지를 알아 보았는데 실제로 Voter들이 어떻게 Authority를 판단하는 부분까지는 다음에 기회가 된다면 작성할 생각이다..
Leave a comment