모르지 않다는 것은 아는것과 다르다.

Spring Security

AuthenticationManager 와 AuthenticationProvider

채마스 2022. 2. 28. 20:32

AuthenticationManager

  • 스프링 시큐리티에서 인증(Authentication)은 AuthenticationManager가 처리한다.

  • AuthenticationProvider 목록 중에서 인증 처리 요건에 맞는 AuthenticationProvider 를 찾아 인증처리를 위임한다.
  • 아래와 같이 부모 ProviderManager 를 설정하여 AuthenticationProvider 를 계속 탐색 할 수 있다.

public interface AuthenticationManager { 
    Authentication authenticate(Authentication authentication) throws AuthenticationException; 
}
  • authentication 이 유효한 인증인지 확인하고, 유효하다면 Authentication 객체를 리턴한다.
  • 대부분 AuthenticationManager 인터페이스를 직접 구현한 ProviderManager 구현체 클래스를 사용한다.
  • ProviderManager 를 구현한 코드를 보면 아래와 같다.
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {

    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();

    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }

        if (debug) {
            logger.debug("Authentication attempt using "
                    + provider.getClass().getName());
        }

        try {
            result = provider.authenticate(authentication);

            if (result != null) {
                copyDetails(authentication, result);
                break;
            }
        }
        catch (AccountStatusException | InternalAuthenticationServiceException e) {
            prepareException(e, authentication);
            // SEC-546: Avoid polling additional providers if auth failure is due to
            // invalid account status
            throw e;
        } catch (AuthenticationException e) {
            lastException = e;
        }
    }
    // ... 이하 생략
}
  • 위의 코드에서 보듯 인증을 처리할 수 있는 Provider 를 순회하며, 찾는 것을 확인할 수 있다.
  • authentication 에 principal , credentials 를 통해서 username 과 password 가 들어온다.
  • 디버깅을 통해서 부모 클래스를 따라가다 보면 UserDetails 를 반환하는 부분을 확인 할 수 있다.
  • 마지막으로는 principal 에는 User인 객체 타입으로 반환하여 Application 에서 사용할 수 있게 한다.





AuthenticationProvider

  • AuthenticationProvider 는 사용자의 이름과 패스워드를 받아서 검증을 하는 클래스이다.
  • AuthenticationManager 가 요청정보를 받아서 해당하는 AuthenticationProvider 를 호출한 후에 요청에 대한 인증처리를 하게된다.
  • 인증이 성공게되면 인증결과를 생성해서 자신을 호출한 AuthenticationManager 에게 인증객체르 전달하는 역할까지 AuthenticationProvider 가 하게된다.
  • 위와 같이 authenticate , supports 두개의 메서드가 존재한다.
    • supports 에서는 폼인증 혹은 Remember Me 등을 처리할 수 있는 조건이 되는지 검증한다.
    • authenticate 에서는 실질적으로 검증을 하게되고 AuthenticationManager와 동일하게 인증객체를 전달받는다.
    • 인증객체 안에는 사용자가 입력한 아이디와 패스워드가 저장되어 있다. 이정보를 가지고 서버에 저장된 해당사용자의 계정과 일치하는지 검증을 하게된다. -> 존재하지 않는 다면? -> UserNotFoundException
    • password 의 경우에 password encoder 클래스로 암호화해서 비교를 한다 -> 다르다면? -> BadCredentialException





AccessDecisionManager

  • Access Control 결정을 내리는 인터페이스, 구현체 3가지를 기본으로 제공한다.
    • AffirmativeBased : 여러 Voter 중에 한명이라도 허용하면 허용, 기본전략
    • ConsensusBased : 다수결
    • UnanimousBased : 만장일치
  • accessDecisionManager() 를 따로 구현하지 않으면, 기본적으로 AffirmativeBased() 를 사용한다.
  • 아래와 같이 AffirmativeBased 코드에서 voter에 의해서 result 값이 생성된다.
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;
        }
    }
    // ... 이하 생략
}






AccessDecisionVoter

  • AccessDecisionVoter 는 3가지 역할을 한다.
    • Authentication이 특정한 Object에 접근할 때 필요한 ConfigAttribute를 만족하는지 확인
    • WebExpressionVoter : 웹 시큐리티에서 사용하는 기본 구현체, ROLE_XXX 일치하는지 확인
    • RoleHierarchyVoter : 계층형 Role 지원
    • 아래와 같이 계층을 설정할 수 있다.
      public AccessDecisionManager accessDecisionManager() {
          RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
          roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
    
          DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
          handler.setRoleHierarchy(roleHierarchy);
    
          WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
          webExpressionVoter.setExpressionHandler(handler);
    
          List<AccessDecisionVoter<? extends Object>> voters = Arrays.asList(webExpressionVoter);
          return new AffirmativeBased(voters);
      }
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
                  .mvcMatchers("/", "/info", "/account/**").permitAll()
                  .mvcMatchers("/admin").hasRole("ADMIN")
                  .mvcMatchers("/user").hasRole("USER")
                  .anyRequest().authenticated()
                  .accessDecisionManager(accessDecisionManager());
    
          http.formLogin();
          http.httpBasic();
      }
    • roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER"); 여기서 roleHierarchy 를 설정해준다.
    • 그 다음 기존에 쓰는 handler 를 로드하고, setRoleHierarchy 를 해준다.




REFERENCES

  • 백기선님의 스프링 시큐리티
  • 정수원님의 스프링 시큐리티