본문 바로가기
스프링 부트/시큐리티

[스프링 시큐리티] AuthenticationManager, AccessDecisionManager

by illlilillil 2022. 5. 23.

AuthenticationManager

사용자 인증 관련 처리를 담당한다. 오직 authenticate라는 메소드만 가진다.

Authentication는 getAuthorities(), getCredentials(), getPrincipal() 등 인증 정보를 가져올 수 있고 setAuthenticated(boolean)를 통해 인증처리를 할 수 있다. 인증 정보가 유효하다면 UserDetailService에서 return한 객체(principal)을 담고있는 Authentication을 리턴한다. 

AccessDecisionVoter들의 투표(vote)결과를 취합하고, 접근 승인 여부를 결정하는 3가지 구현체를 제공한다.

  • AffirmativeBased(default) — AccessDecisionVoter가 승인하면 이전에 거부된 내용과 관계없이 접근이 승인 
  • ConsensusBased — 다수의 AccessDecisionVoter가 승인하면 접근이 승인
  • UnanimousBased — 모든 AccessDecisionVoter가 만장일치로 승인해야 접근이 승인

ProviderManager

기본 구현체는 ProviderManager를 사용한다. 1개 이상의 AuthenticationProvider 인터페이스 구현체로 구성된다. ProviderManager는 직접 처리가 아닌 여러 Provider 중에 적절한 Provider를 선택하는 역할을 한다. 만약 적절한 Provider가 없을 경우 parent에게 위임하여 처리하도록 한다.

DaoAuthenticationProvider는 직접 구현한 UserDetailsService를 사용한다.

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

인증 프로세스는 다음과 같다.

1. 유저 생성 및 로그인 요청을 하게 됨

2. ProviderManager의 authentication으로 진입하게 된다. Authentication에는 username(principal), password(credentials)가 들어온다. authorities는 아직 모르기에 null 값이다.

3. 요청을 처리할 수 있는 적절한 provider를 찾아 처리하고 인증정보가 담긴 객체를 반환한다.

4. 인증 완료 후 반환 객체는 SecurityContext에 저장된다.

 

AccessDecisionManager

사용자가 보호받는 리소스에 접근할 수 있는 적절한 권한이 있는지 확인한다. voter 개념이 있어 의사결정을 하는데 사용된다.

decide는 반드시 구현해야 한다. 기본 전략으로 AffirmativeBased를 사용한다. 그 외에도 여러 전략을 제공한다.

  • AffirmativeBased — AccessDecisionVoter가 승인하면 이전에 거부된 내용과 관계없이 접근이 승인
  • ConsensusBased — 다수의 AccessDecisionVoter가 승인하면 접근 승인
  • UnanimousBased — 모든 AccessDecisionVoter가 만장일치로 승인해야 접근 승인
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private List<AccessDecisionVoter<?>> decisionVoters;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private boolean allowIfAllAbstainDecisions = false;
    
    ... ... ... ...

    public boolean supports(ConfigAttribute attribute) {
        Iterator var2 = this.decisionVoters.iterator();

        AccessDecisionVoter voter;
        do {
            if (!var2.hasNext()) {
                return false;
            }

            voter = (AccessDecisionVoter)var2.next();
        } while(!voter.supports(attribute));

        return true;
    }

    public boolean supports(Class<?> clazz) {
        Iterator var2 = this.decisionVoters.iterator();

        AccessDecisionVoter voter;
        do {
            if (!var2.hasNext()) {
                return true;
            }

            voter = (AccessDecisionVoter)var2.next();
        } while(voter.supports(clazz));

        return false;
    }

    public String toString() {
        return this.getClass().getSimpleName() + " [DecisionVoters=" + this.decisionVoters + ", AllowIfAllAbstainDecisions=" + this.allowIfAllAbstainDecisions + "]";
    }
}

AffirmativeBased - 하나라도 voter가 성공하면 인가된다.

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;
        Iterator var5 = this.getDecisionVoters().iterator();

        while(var5.hasNext()) {
            AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
            int result = voter.vote(authentication, object, configAttributes);
            switch(result) {
            case -1:
                ++deny;
                break;
            case 1:
                return;
            }
        }

        if (deny > 0) {
            throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        } else {
            this.checkAllowIfAllAbstainDecisions();
        }
    }
}

AccessDecisionVoter

AccessDecisionVoter의 기본전략은 WebExpressionVoter으로 prefix로 ROLE_XXX가 사용된다.

특정 Object에 접근할때 필요한 ConfigAttributes들을 만족하는지 확인한다.

권한 허용이나 인가 등이 configAttributes가 되고 configure에서 지정한 리소스가 Object가 된다.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/me").hasAnyRole("USER", " ADMIN")
            .antMatchers("/admin").access("hasRole('ADMIN') and isFullyAuthenticated()")
            .anyRequest().permitAll()
            .and()
            .formLogin()
            .defaultSuccessUrl("/")
            .permitAll()
            .and()
            .rememberMe()
            .rememberMeParameter("remember-me")
            .tokenValiditySeconds(300)
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/")
            .invalidateHttpSession(true)
            .clearAuthentication(true)
            .and()
            .requiresChannel()
            .anyRequest().requiresSecure()
            .and()
            .anonymous()
            .principal("thisIsAnonymousUser")
            .authorities("ROLE_ANONYMOUS", "ROLE_UNKNOWN")
    ;
}

 

댓글