Java & Spring

Spring Security를 이용한 JWT 인증(1)

칼퇴시켜주세요 2022. 2. 9. 01:04
728x90

웹개발을 할 경우 무조건 처리해야 하는 부분인 회원가입/로그인 인데요. 오늘은 Spring Security를 이용하여 JWT를 생성하고 사용자 인증 처리에 대해 알아보도록 하겠습니다. 회원가입과 로그인의 자세한 내용은 Spring RESTful 회원가입/로그인에서 확인할 수 있습니다.

[ Spring Security란? ]


Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크입니다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리하고 있습니다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있습니다.

Spring Security Filter에 대해 아래 그림으로 간단히 설명하겠습니다. (지금도 100% 이해하지는 못했지만)

Filter사용에 있어서 중요한 개념이 많지만 서브릿 컨테이너와 필터에대한 자세한 내용은 따로 정리하도록 하겠습니다. 서브릿 컨테이너와 필터(참고)

1. Http Request를 받으면 서브릿 필터 체인에 등록된 Spring Security Filter가 동작합니다. Security Filter 종류에 대해 알고싶다면 Security Filter 목록을 참고하면 됩니다.

2. UsernamePasswordAuthenticationToken은 Authentication을 implements한 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 { }


3. AuthenticationProvider는 실제 인증에 대한 부분을 처리하는 곳 입니다. 인증 전의 Authentication객체를 받아서 인증이 완료된 객체를 반환하는 역할을 하며 CustomAuthenticationProvider 구현 시 AuthenticationProvider 인터페이스를 implements 하여 작성한 후 AuthenticationManager에 등록하면 됩니다.
(저는 JwtAuthenticationFilter와 JwtTokenProvider를 따로 구현하여 구현하여 사용하였습니다.) -> 다음 글에서 설명

public interface AuthenticationProvider { 
	// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환 
	Authentication authenticate(Authentication var1) throws AuthenticationException; 
    
	boolean supports(Class<?> var1); 
}


4. AuthenticatonManager는 AuthenticationProvider를 리스트로 가지고 있는 형태로 ProviderManager에서 AuthenticatonManager를 impements 받아 Provider List를 for문을 통해 조회하면서 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; 
	} 
}


위에서 설명한 ProviderManager에 우리가 직접 구현한 CustomAuthenticationProvider를 등록하는 방법은 WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 할 수 있습니다. WebSecurityConfigurerAdapter의 상위 클래스에서는 AuthenticationManager를 가지고 있기 때문에 우리가 직접 만든 CustomAuthenticationProvider를 등록할 수 있습니다.

@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()); 
    } 
}


5. SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프래그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장됩니다. SecurityContextHolder는 기본적으로 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 방법과SecurityContextHolder.MODE_THREADLOCAL 방법을 제공합니다.


6. SecurityContextAuthentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있습니다.

7. Authentication는 현재 접근하는 주체의 정보와 권한을 담는 인터페이스입니다. Authentication 객체는 Security Context에 저장되며, SecurityContextHolder를 통해 SecurityContext에 접근하고, SecurityContext를 통해 Authentication에 접근할 수 있습니다.

 


요약

기본적으로 Spring Security의 로그인 인증은 UsernamePasswordAuthenticationFilter에서 수행되며 UsernamePasswordAuthenticationToken을 통해 Authentication객체를 생성합니다. 이후 AuthenticationProvider에서 Authentication 객체의 인증작업을 거친 후 인증이 완료된 Authentication 객체를 SecurityContextHolder에 저장합니다. 다음 포스트에는 JwtAuthenticationFilter와  JwtTokenProvider를 이용하여 JWT 토큰 인증 인가 방법에 대해 알아보도록 하겠습니다. 

반응형

'Java & Spring' 카테고리의 다른 글

Spring WebSocket을 이용한 Chatting Server 구현(1)  (0) 2022.04.04
Spring vs Spring Boot  (0) 2022.02.07