Spring Security

3 분 소요

0. 들어가면서

Spring Security ??

Spring Security는 강력하고 사용자 정의가 매우 용이한 인증 및 액세스 제어 프레임워크입니다. 이는 Spring 기반 애플리케이션의 보안을 위한 사실상의 표준입니다. Spring Security는 Java 애플리케이션에 인증과 인증을 모두 제공하는 데 중점을 둔 프레임워크입니다.


Authentication / Authorization

  • Principal & Credential pattern : username & password
  • Authentication 제출 -> 검증 -> Authorization 부여


용어 설명

  • Authentication : 사용자가 본인이 맞는지 확인하는 절차
  • Authorization : 인증된 사용자가 요청한 자원에 접근 가능한지 결정하는 절차
  • Principal : 요청한 자원에 접근하는 대상
  • Credential : 자원에 접근하는 대상의 비밀번호
  • SecurityContext : Authentication 객체를 보관, 관리, 불러오는 역할


Flowchart

image info



1. HTTP Request Receive

  • Browser(Client)로 부터 request를 받음
  • id, password를 기반으로 인증, 권한 부여를 진행
  • Application Filter 중에 Authentication Filters에 가장 먼저 도달



2. AuthenticationFilter

  • AuthenticationFilter가 인증, 권한 부여를 위한 과정을 먼저 거침
  • 위의 과정을 마친 후 Dispatcher Servlet으로 요청을 넘김



3. UsernamePasswordAuthenticationToken

  • Authentication Interface의 구현체
  • Principal-Credential pattern을 반영한 객체
  • 생성자를 통해서 인증 전의 Authentication 객체, 인증 후의 Authentication 객체를 생성
  • 모든 접근 주체는 Authentication을 생성하고 SecurityContext에 보관 및 사용


  • AbstractAuthenticationToken implements Authentication
  • UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken


Authentication Code

public interface Authentication extends Principal, Serializable {

    //인증된 사용자의 권한 목록
    Collection<? extends GrantedAuthority> getAuthorities();

    //사용자 비밀번호
    Object getCredentials();
    
    //사용자 상세정보
    Object getDetails();
    
    //사용자 아이디
    Object getPrincipal();
    
    //사용자 인증 여부
    boolean isAuthenticated();
    
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}


UsernamePasswordAuthenticationToken Code

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    
    //사용자 아이디
    private final Object principal;

    //사용자 비밀번호
    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);
	}
}


AbstractAuthenticationToken Code

public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}



4. AuthenticationManager

  • authenticate 메소드를 제공하는 interface
  • Authentication 객체를 받아 인증하고 성공 시 Authentication 객체를 return
  • AuthenticationManager에 등록된 AuthenticationProvider에 의해 인증 처리가 이루어짐


  • 인증 성공 시 인증이 성공한 Authentiction 객체를 생성하여 SecurityContext에 저장
  • 인증 실패 시 AuthenticationException을 발생시킴


AuthenticationManager Code

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}



5. ProviderManager(implements AuthenticationManager)

  • AuthenticationManger의 구현체
  • Spring Security가 관리하는 Bean
  • ProviderManager는 여러 AuthenticationProvider를 순회하면서 인증 처리가 가능한 AuthenticationProvider를 찾아서 인증 처리 과정 위임


ProviderManager Code

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 = nul;
	    boolean debug = logger.isDebugEnabled();

      //AuthenticationProvider 중에 가능한 provider에서 result를 얻어낸다
		  for (AuthenticationProvider provider : getProviders()) {
			try {
				result = provider.authenticate(authentication);

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


SecurityConfig에서 직접 customize한 provider를 등록할 수 있음(빈 등록 후 ProviderManager에 AuthenticationProvider로 등록)

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



6. AuthenticationProvider

  • AuthenticationProviders 중에 실제 검증을 수행하는 AuthenticationProvider
  • principal을 바탕으로 해당 유저의 정보를 조회
  • UserDetailService 구현체를 사용하여 유저의 정보가 담겨있는 객체를 반환


  • 인증 성공 시 인증하기 전의 Authentication 객체를 받아서 인증이 완료된 Authentication 객체로 반환
  • 인증 실패 시 AuthenticationException 발생


AuthenticationProvider Code

public interface AuthenticationProvider {

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

  boolean supports(Class<?> var1); 
}



7. UserDetailsService

  • DB에서 유저 정보를 불러와서 유저의 정보가 담겨있는 객체를 만듬


UserDetailService Code

public interface UserDetailsService {

  UserDetails fetchUserDetailByUserId(String userId) throws UsernameNotFoundException;

}


UserDetail Code

public interface UserDetails extends Serializable {

  Collection<? extends GrantedAuthority> getAuthorities();

  String getPassword();

  String getUsername();

  boolean isAccountNonExpired();

  boolean isAccountNonLocked();

  boolean isCredentialsNonExpired();

  boolean isEnabled();
    
}