Spring Security

정미·2022년 7월 12일
0

Computer Science

목록 보기
24/81

Spring 기반의 어플리케이션의 보안(인증, 인가, 권한)을 담당하는 프레임워크

특징

  • 보안과 관련된 체계적으로 많은 옵션들을 지원해주기 때문에 개발자가 일일이 보안관련 로직을 작성하지 않아도 된다.
  • Filter Chain 기반 동작
    • Spring MVC와 분리되어 관리 및 동작
  • Credential 기반의 인증 방식 사용

용어

  • Principal 접근 주체
    • 보호받는 대상(Resource)에 접근하는 유저
  • Credential 비밀번호
    • Resource에 접근하는 대상에 비밀번호
  • 권한
    1. 웹 요청 권한
    2. 메소드 호출 및 도메인 인스턴스에 대한 접근 권한

아키텍처

  1. 유저가 로그인을 시도한다. (Http Request)
  2. AuthenticationFilter가 요청을 가로챈다. 사용자가 입력한 정보를 바탕으로 UsernamePasswordAuthenticationToken 객체(미검증 상태)를 생성한다.
  3. AuthenticationManager의 구현체인 ProviderManager에게 UsernamePasswordAuthenticationToken 를 전달한다.
  4. AuthenticationProvider에게 UsernamePasswordAuthenticationToken 를 전달한다.
  5. 실제 DB로부터 사용자 인증 정보를 가져오는 UserDetailsService에게 사용자 정보를 넘겨준다.
  6. DB에 있는 유저라면 UserDetails로 받아온다.
  7. AuthenticationProvider는 사용자 정보와 UserDetails를 비교한다.
  8. 인증이 완료되면 사용자 정보를 담은 Authentication을 반환한다.
  9. 최초의 AuthenticationFilterAuthentication이 반환된다.
  10. Spring Security의 in-memory 세션 저장소인 SecurityContextHolderAuthentication을 저장한다.

실질적인 인증 과정은 사용자가 입력한 데이터와 UserDetailService의 메서드가 반환하는 UserDetails 객체를 비교함으로써 동작한다. 두 클래스의 구현에 따라 인증의 세부 과정이 달라진다.

주요 모듈

1. SecurityContextHolder

  • 보안 주체의 세부 정보를 포함한 응용 프로그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다.
  • 기본적으로 SecurityContextHolder.MODE_INHERITABLETHREADLOCALSecurityContextHolder.MODE_THREADLOCAL 방법을 제공

2. SecurityContext

  • Authentication(3)을 보관

3. Authentication

  • 현재 접근하는 주체의 정보와 권한을 담는 인터페이스
  • SecurityContext에 저장됨
  • SecurityContextHolder(1)를 통해 SecurityContext(2)에 접근하고, SecurityContext(2)를 통해 Authentication에 접근 가능
public interface Authentication extends Principal, Serializable {
    // 현재 사용자의 권한 목록을 가져온다.
    Collection<? extends GrantedAuthority> getAuthorities();
    
    // credentials(주로 비밀번호)을 가져온다.
    Object getCredentials();
    
    Object getDetails();
    
    // Principal 객체를 가져온다.
    Object getPrincipal();
    
    // 인증 여부를 가져온다.
    boolean isAuthenticated();
    
    // 인증 여부를 설정한다.
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

4. UsernamePasswordAuthenticationToken

  • Authentication(3)을 구현한 AbstractAuthenticationToken의 하위 클래스
  • User의 ID = Principal
  • Password = Credential
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    // 주로 사용자의 ID에 해당함
    private final Object principal;
    // 주로 사용자의 PW에 해당함
    private Object credentials;
    
    // 인증 완료 전의 객체 생성
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
			super(null);
			this.principal = principal;
			this.credentials = credentials;
			setAuthenticated(false);
		}
    
    // 인증 완료 후의 객체 생성
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
			super(authorities);
			this.principal = principal;
			this.credentials = credentials;
			super.setAuthenticated(true); // must use super, as we override
		}
}

public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}

5. AuthenticationProvider

  • AuthenticationProvider 인터페이스를 구현한 CustomAuthenticationProvider에서 실제 인증을 처리한다.
public interface AuthenticationProvider {

	// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);
    
}
  • 인증 전의 Authentication 객체를 받아 인증이 완료된 객체를 반환한다.
  • 구현체를 작성하여 AuthenticationManager(6)에 등록하면 된다.
    • WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 직접 구현한 provider를 등록 가능하다.
      - WebSecurityConfigurerAdapter의 상위 클래스는 AuthenticationManager(6)을 가지고 있기 때문

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
          @Bean
          public AuthenticationManager getAuthenticationManager() throws Exception {
              return super.authenticationManagerBean();
          }
            
          @Bean
          public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
              return new CustomAuthenticationProvider();
          }
          
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.authenticationProvider(customAuthenticationProvider());
          }
      }

6. AuthenticationManager

  • AuthenticationManager에 등록된 AuthenticationProvider(5)에 의해 실질적인 인증이 처리된다.
  • 인증이 성공하면 UsernamePasswordAuthenticationToken(4)의 2번째 생성자를 이용하여 isAuthenticated=true인 객체를 생성하여 SecurityContext(2)에 저장한다.
    • 인증에 실패할 경우 AuthenticationException을 발생시킨다.
  • 인증 상태를 유지하기 위해 세션에 보관한다.
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) 
		throws AuthenticationException;
}
  1. ProviderManager

    • AuthenticationManager(6) 인터페이스를 구현
    • 실제 인증 과정 로직을 가지고 있는 AuthenticationProvider(5) list를 가지고 있고, for문을 통해 모든 provider를 조회 및 authenticate 처리를 한다.
    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) {
    					prepareException(e, authentication);
    					// SEC-546: Avoid polling additional providers if auth failure is due to
    					// invalid account status
    					throw e;
    				}
                ....
    			}
    			throw lastException;
    		}
    }

7. UserDetails

  • 인증에 성공하여 생성된 객체
  • Authentication(3)을 구현한 UsernamePasswordAuthenticationToken(4)을 생성하기 위해 사용된다.
  • 직접 개발한 UserVO 모델에 UserDetails를 implements하여 처리
public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
    
}

8. UserDetailsService

  • UserDetails(7)을 반환하는 단 하나의 메소드만 가지고 있다.
  • 일반적으로 이를 구현한 클래스 내부에 UserRepository를 주입받아 DB와 연결하여 처리한다.
public interface UserDetailsService {

    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

}

9. Password Encoding

  • AuthenticationManagerBuilder.userDetailsService().passwordEncoder()를 통해 패스워드 암호화에 사용될 PasswordEncoder 구현체를 지정 가능하다.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	// TODO Auto-generated method stub
	auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder(){
	return new BCryptPasswordEncoder();
}

10. GrantedAuthority

  • 현재 사용자(Principal)가 가지고 있는 권한
    • ‘roles’라고도 함
  • ROLE_ADMIN, ROLE_USER 같이 ROLE_*의 형태로 사용한다.
  • UserDetailsService(8)에 의해 불러올 수 있다.
  • 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.

Filters

  • Spring Security에서 동작하는 기본 필터들의 목록 및 순서
    • 특정 필터들을 명시적으로 포함시키거나 제외시킬 수 있다.
    • javax.servlet.Filter 인터페이스를 직접 구현하여 커스텀 필터를 추가 가능하다.

아래는 auto-config attribute를 설정하였을 경우 자동으로 구성되는 필터들이다.

  1. SecurityContextPersistenceFilter
    • SecurityContextRepository에서 SecurityContext를 로딩하거나 저장하는 역할
    • SecurityContext: 사용자의 보호 및 인증된 세션
  2. LogoutFilter
    • 로그아웃 URL(default: /j_spring_security_logout)로의 요청을 감시하여 해당 사용자를 로그아웃시킨다.
  3. UsernamePasswordAuthenticationFilter
    • 아이디와 비밀번호를 사용하는 폼기반 인증 요청 URL(default: /j_spring_security_checkt)를 감시하여 사용자를 인증하는 역할
  4. DefaultLoginPageGeneratingFilter
    • 폼 또는 OpenID 기반 인증을 위한 로그인폼 URL(default: /spring_security_login)을 감시하고 관련된 로그인폼을 생성한다.
  5. BasicAuthenticationFilter
    • HTTP 기본 인증 헤더를 감시하여 처리한다.
  6. RequestCacheAwareFilter
    • 로그인 성공 후 원래 요청 정보를 재구성하기 위해 사용된다.
  7. SecurityContextHolderAwareRequestFilter
    • HttpServletRequestWrapper를 상속한 SecurityContextHolderAwareRequestWrapper의 클래스
    • HttpServletRequest 정보를 감싼다.
    • 필터 체인상의 다음 필터들에게 부가 정보를 제공한다.
  8. AnonymousAuthenticationFilter
    • 해당 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증 토큰에 사용자는 익명 사용자로 나타난다.
  9. SessionManagementFilter
    • 인증된 사용자와 관련된 모든 세션 추적
  10. ExceptionTranslationFilter
    • 보호된 요청을 처리하는 중 발생할 수 있는 예외를 위임하거나 전달
  11. FilterSecurityInterceptor
    • AccessDecisionManager로 권한부여 처리를 위임하여 접근 제어 결정을 쉽게 해준다.


출처

1개의 댓글

comment-user-thumbnail
2022년 7월 14일

또한 사용자가 입력한 데이터와 UserDetailService가 반환한 UserDetails 개체를 비교하여 실제 유효성 검사가 작동하는지 확인했습니다. 그 점에서 세부적인 검증 과정은 2계층 구현에 따라 다르다.

답글 달기