ahnnyung ,/Spring

[Spring] Spring Security

hi,ho 2022. 3. 25. 13:22
반응형

개요

Application의 보안(Authentication & Authorization)을 담당하는 스프링 하위 프레임워크이다. Spring Security는 인증과 인가에 대한 부분을 Filter 흐름에 따라 처리하고 있다. 보안과 관련해서 체계적으로 다양한 옵션들을 제공해주고 있다.

Authentication & Authorization

Authentication은 인증 (로그인), Authorization은 인가(접근 허용)을 의미한다. Authentication은 그야말로 접근자(사용자)의 신원을 확인하는 프로세스이고, Authorization누가 무엇을 할 수 있는지 결정하는 규칙이다.Spring Security는 기본적으로 인증(Authentication)절차를 거친 후에 인가(Authorization)절차를 진행하게 되며, 인가 과정에서 리소스에 대한 접근 권한(ROLE)이 있는지 확인을 하게 된다.

Spring Security에서는 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다.

Spring Security Authentication Architecture

  1. User의 로그인 요청이 HttpRequest를 통해 날라온다.
  2. AuthenticationFilter가 request에서 사용자가 보낸 정보를 intercept한다. 이 정보를 가지고 인증용객체인 UsernamePasswordAuthenticationToken 객체를 만들어서 AuthenticationManager 인터페이스로 전달한다.
  3. AuthenticationManager는 실제 인증을 진행할 AuthenticationProvider에게 Authentication객체를 전달한다.
  4. AuthenticationProvider는 Authentication 객체를 가지고 userDetailsService를 통해서 DB에 있는 User임이 확인이 되면 UserDetails 객체로 생성한다.
  5. 반환된 UserDetails는 Authentication 객체 안에 담기게 되고, SpringSecurity의 인메모리저장소인 SecurityContextHolder에 저장하게 된다.

 

인증(Authentication)

Authentication

Authentication은 현제 접근하는 주체의 정보와 권한을 담는 인터페이스다. Authentication 객체는 Security Context안에 저장된다.

SecurityContext & SecurityContextHolder

SecurityContext는 Authentication을 보관하는 역할을 하며 SecurityContext를 통해 Authentication에 접근 할 수 있다. 이러한 SecurityContext는 SecurityContextHolder를 통해 접근하다.

UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationTokenAuthentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, userid가 Principal역할을 하고, Password가 Credential역할을 한다.

인증의 전후로 생성될 때 사용되는 생성자가 다르다. 인증 전과 후의 생성자 차이는 Authority 정보를 포함해서 생성하는지에 대한 차이가 있다.

실제 인증과정 후에는 UserDetails(혹은 CustomUserDetails, 일반 로그인) 객체나 OAuth2User(OAuth로그인)객체가 principal 변수에 저장된다

AuthenticationManager의 인증 처리

Spring Security는 인증이 필요할 때 o.s.s.authentication.AuthenticationManager를 이용한다. (실질적으로는 AuthenticationManager에 등록된 AuthenticationProvider에 의해 처리된다.)

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

authenticate() 메소드는 인증하는데 필요한 정보를 담은 Authentication 객체를 입력으로 전달받는다. 인증에 성공하면 인증 정보를 담은 Authentication 객체를 리턴하고, 그렇지 않을 경우 Exception을 발생한다.

Spring이 제공하는 AuthenticationManager 인터페이스의 구현 클래스로 ProviderManager클래스를 제공하고 있는데, 이 클래스는 인증 처리를 AuthenticationProvider에게 위임한다.

ProviderManager는 한 개 이상의 AuthenticationProvider를 가질 수 있으며, 다음과 같은 방식으로 동작한다.

  1. 등록된 AuthenticationProvider에 대해 차례대로 다음 과정을 실행한다.
    1. authenticate() 메소드를 실행하면서 인증 처리를 요청한다.
    2. authenticate()가 Authentication 객체를 리턴하면, 해당 객체를 리턴한다.
  2. 어떤 AuthenticationProvider도 인증에 성공하지 못한 경우 Exception을 발생한다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    public List<AuthenticationProvider> getProviders() {
            return providers;
        }

    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
            Class<? extends Authentication> toTest = authentication.getClass();
            AuthenticationException lastException = null;
            Authentication result = null;
            boolean debug = logger.isDebugEnabled();
            //for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.
            for (AuthenticationProvider provider : getProviders()) {
                ....
                try {
                    result = provider.authenticate(authentication);

                    if (result != null) {
                        copyDetails(authentication, result);
                        break;
                    }
                }
                catch (AccountStatusException e) {
                    ....
                    throw e;
                }
          ....
            }
            throw lastException;
        }
}

보편적으론 ProviderManager를 그대로 사용하고 AuthenticationProvider를 구현하는 방식을 택한다. 직접 구현한 CustomAuthenticationProvider를 등록하는 방법은 WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 할 수 있다.

UserDetails

인증에 성공하여 생성된 Authentication 객체 안에는 인증 성공한 사용자에 대한 UserDetails가 생성된다. 이 UserDetails는 다음과 같은 정보를 반환하는 메소드를 가지고 있다. UserDetails 인터페이스의 경우 직접 설계한 사용자의 정보를 담고있는 UserVO모델에 UserDetails를 implements 하거나, UserDetailsVO에 UserDetails를 implements 하여 처리할 수 있다.

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

 

UserDetailsService

UserDetailsService 인터페이스는 UserDetails 객체를 반환하는 단 하나의 메소드를 가지고 있는데, 일반적으로 이를 implements 하여 클래스의 내부에 UserRepository를 주입받아 이를 통해 DB와 연결하여 처리한다.

 

적용

SecurityConfig 등록

예시를 위한 설계.

GET 메소드에 대해서는 전체 접근 허용

POST, DELETE, PUT 메소드에 대해서는 ADMIN, MANAGER 권한을 가진 user에게만 접근 허용

 

PrincipalDetails(CustomUserDetails)

...

 

PrincipalDetailsService(CustomUserDetailsService)

...

 

 

정리

다시 한 번 그림을 보자.

  1. Http요청을 통해 들어온 정보를 가지고 AuthenticationFilter에서 AuthenticationManager (PorviderManager)로 인증하는 과정을 요청한다.
  2. ProviderManager는 한 개 이상의AuthenticationProvider를 가지고 있고, 이들에게 각각 인증처리를 요청한다.
  3. 만약 인증 정보가 있다면 UserDetailsService를 통하여 인증에 성공한 사용자에 대한 UserDetails 정보를 받아온다.
  4. 받아온 Authority 정보가 담겨진 Authentication 객체가 ProviderManager에게 전달되고,ProviderManager가 다시 Authentication 객체를 리턴해주게 되고, 이 정보가 SecuritContextHolder를 통해 SecurityContext 내부에 저장되게 된다.
  5. 저장된 Authentication을 활용하여 Authorization 절차가 진행되게 된다.
반응형