[Section 4] Spring Security 인증 구성요소

Kim·2022년 11월 21일
0

Boot Camp

목록 보기
52/64
post-thumbnail

안전하게 보호된 애플리케이션을 개발하기 위해 반드시 익혀야 되는 중요한 보안 요소는 두 가지인데, 그 중 하나는 바로 인증(Authentication)이다.
Spring Security에서는 Spring Security Filter Chain을 통해 보안을 위한 특정 작업을 처리한다는 것을 알게 되었다. 그럼, Spring Security Filter Chain에 사용자의 인증 요청이 전달되었다면 이후의 처리는 어떻게 될까?


Spring Security의 인증 처리 흐름

컴포넌트로 보는 인증 처리 흐름

사용자가 로그인 인증을 위한 요청을 전송할 경우, Spring Security에서 해당 인증 요청을 어떻게 처리하는지를 한 눈에 볼 수 있는 Spring Security 핵심 컴포너트로 구성된 인증 처리 흐름이다.

가장 일반적으로 사용하는 인증 방식이 ID, Password(Spring Security의 Username, Password)를 이용한 로그인 인증 방식이기 때문에, 로그인 인증에 대한 처리 흐름으로 구성했다.

  • (1)에서 사용자가 로그인 폼 등을 이용해 Username(ID)과 Password를 포함한 request를 Spring Security가 적용된 애플리케이션에 전송한다.
    사용자의 로그인 요청이 Spring Security의 Fiter Chain까지 들어오면, 여러 Filter들 중에서 UsernamePasswordAuthenticationFilter가 해당 요청을 전달 받는다.

  • 사용자의 로그인 요청을 전달 받은 UsernamePasswordAuthenticationFilter는 Username과 Password를 이용하여 (2)와 같이 UsernamePasswordAuthenticationToken을 생성한다.
    UsernamePasswordAuthenticationToken Authentication 인터페이스를 구현한 클래스이다. Authentication은 아직 인증되지 않았다.

  • 인증되지 않은 Authentication을 갖고 있는 UsernamePasswordAuthenticationFilter는 (3)과 같이 해당 AuthenticationAuthenticationManager에게 전달한다.
    AuthenticationManager는 인증 처리를 총괄하는 인터페이스로, 매니저 역할을 한다. AuthenticationManager룰 구현한 인터페이스는 ProviderManager이다. 즉, ProviderManager가 인증 작업을 총괄하는 실질적인 매니저이다.

    💡 현실에서의 매니저 혹은 지배인의 역할을 떠올려보면 이해가 쉬울 것이다.
    레스토랑의 매니저는 손님에게 직접 주문을 받거나, 음식을 서빙하는 등의 일을 하지 않는다. 매니저는 레스토랑에서 일어나는 모든 일을 총괄하고 관리하는 역할을 한다.
    Spring Security의 ProviderManager 역시, 직접 인증을 처리하지 않고 인증을 처리할 누군가를 찾아 인증 처리를 맡긴다. 인증을 처리할 누군가는 AuthenticationProvider이다.

  • (4)와 같이 ProviderManager으로부터 Authentication을 전달 받은 AuthenticationProviderUserDetailsService를 이용해 UserDetails를 조회한다.
    UserDetails는 DB 등 저장소에 저장된 사용자의 Username과 사용자 자격을 증명해주는 크리덴셜(Credential)인 Password, 그리고 사용자의 권한 정보를 포함하고 있는 컴포넌트이다.
    UserDetails 를 제공하는 컴포넌트가 바로 UserDetailsService이다.

  • UserDetailsService는 (5)에서 처럼 DB 등 저장소에서 사용자의 크리덴셜(Credential)을 포함한 사용자의 정보를 조회한다. (6)

  • 저장소에서 조회한 사용자의 크리덴셜을 포함한 사용자 정보를 기반으로 (7)과 같이 UserDetails를 생성한 후, 생성된 UserDetails를 다시 AuthenticationProvider에게 전달한다. (8)

  • UserDetails를 전달 받은 AuthenticationProviderPasswordEncoder를 이용해 UserDetails에 포함된 암호화 된 Password와 인증을 위한 Authentication안에 포함된 Password가 일치하는지 검증한다.
    검증에 성공하면 UserDetails를 이용해 인증된 Authentication을 생성한다. (9)
    검증에 실패하면 Exception을 발생시키고 인증 처리를 중단한다.

  • AuthenticationProvider는 인증된 AuthenticationProviderManager에게 전달한다. (10)
    (2)에서의 Authentication은 인증을 위해 필요한 사용자의 로그인 정보를 가지고 있지만, 이 단계에서 ProviderManager에게 전달한 Authentication은 인증에 성공한 사용자의 정보(Principal, Credential, GrantedAuthorities)를 가지고 있다.

  • 이제 ProviderManager는 (11)과 같이 인증된 Authentication을 다시 UsernamePasswordAuthenticationFilter에게 전달한다.

  • 인증된 Authentication을 전달 받은 UsernamePasswordAuthenticationFilter는 마지막으로 (12)와 같이 SecurityContextHolder를 이용해 SecurityContext에 인증된 Authentication을 저장한다.
    SecurityContext는 Spring Security의 세션 정책에 따라서 HttpSession에 저장되어 사용자의 인증 상태를 유지하기도 하고, HttpSession을 생성하지 않고 무상태를 유지하기도 한다.

🔑Key Summary

  • 사용자의 로그인 요청을 처리하는 Spring Security Filter는 UsernamePasswordAuthenticationFilter이다.

  • UsernamePasswordAuthenticationTokenAuthentication 인터페이스를 구현한 구현 클래스이며, 여기서의 Authentication은 아직 인증이 되지 않은 Authentication을 의미한다.

  • AuthenticationManager는 인증 처리를 총괄하는 매니저 역할을 하는 인터페이스이고, AuthenticationManager를 구현한 구현 클래스가 ProviderManager이다.

  • UserDetails는 데이터베이스 등의 저장소에 저장된 사용자의 Username과 사용자의 자격을 증명해주는 크리덴셜(Credential)인 Password, 그리고 사용자의 권한 정보를 포함하고 있는 컴포넌트이다.

  • UserDetails를 제공하는 컴포넌트가 바로 UserDetailsService이다.

  • UserDetailsService는 데이터베이스 등의 저장소에서 사용자의 크리덴셜(Credential)을 포함한 사용자의 정보를 조회하여 AuthenticationProvider에게 제공한다.

  • UsernamePasswordAuthenticationFilter가 생성하는 Authentication은 인증을 위해 필요한 사용자의 로그인 정보를 가지고 있지만, AuthenticationProvider가 생성한 Authentication은 인증에 성공한 사용자의 정보(Principal, Credential, GrantedAuthorities)를 가지고 있다.

  • 인증된 Authentication을 전달 받은 UsernamePasswordAuthenticationFilterSecurityContextHolder를 이용해 SecurityContext에 인증된 Authentication을 저장한다. SecurityContext는 다시 HttpSession에 저장되어 사용자의 인증 상태를 유지한다.


Spring Security의 인증 컴포넌트

UsernamePasswordAuthenticationFilter

Spring Security의 인증 처리 흐름 그림에서 로그인 request를 제일 먼저 만나는 컴포넌트는 Spring Security Filter Chain의 UsernamePasswordAuthenticationFilter이다.

UsernamePasswordAuthenticationFilter는 일반적으로 로그인 폼에서 제출되는 Username과 Password를 통한 인증을 처리하는 Filter이다.
클라이언트로부터 전달 받은 Username과 Password를 Spring Security가 인증 프로세스에서 이용할 수 있게 UsernamePasswordAuthenticationToken을 생성한다.

UsernamePasswordAuthenticationFilter 클래스의 역할

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // (1)

	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; // (2)

	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; // (3)

	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST"); // (4)

  ...

	public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager); // (5)
	}

    // (6)
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
    	// (6-1)
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);
        
    ...
		
    // (6-2)
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
		...
        
		return this.getAuthenticationManager().authenticate(authRequest); // (6-3)
	}
	...
    
}
  • UsernamePasswordAuthenticationFilter는 (1)과 같이 AbstractAuthenticationProcessingFilter를 상속한다.

  • UsernamePasswordAuthenticationFilter 클래스에는 doFilter() 메서드가 존재하지 않는다.
    Filter가 Filter의 역할을 하기 위해서는 doFilter() 메서드가 있어야 하는데, doFilter() 메서드는 어디에 있을까?
    상위 클래스 AbstractAuthenticationProcessingFilter가 doFilter() 메서드를 포함하고 있다.
    사용자의 로그인 request를 제일 먼저 전달 받는 클래스는 UsernamePasswordAuthenticationFilter의 상위 클래스인 AbstractAuthenticationProcessingFilter 클래스인 것이다.

  • (2)와 (3)을 통해 클라이언트의 로그인 폼을 통해 전송되는 request parameter의 디폴트 name은 usernamepassword인 것을 알 수 있다.

  • (4)의 AntPathRequestMatcher는 클라이언트의 URL에 매치되는 매처이다.
    클라이언트의 URL이 /login이고 HTTP Methid가 POST인 경우, 매치될 것이라고 예상할 수 있다.

    (4)에서 생성되는 AntPathRequestMatcher의 객체(DEFAULT_ANT_PATH_REQUEST_MATCHER)는 (5)에서 상위 클래스인 AbstractAuthenticationProcessingFilter 클래스에 전달되어 Filter가 구체적인 작업을 수행할지 특별한 작업 없이 다른 Filter를 호출할지 결정하는데 사용된다.

  • (5)에서 AntPathRequestMatcher의 객체(DEFAULT_ANT_PATH_REQUEST_MATCHER)와 AuthenticationManager를 상위 클래스인 AbstractAuthenticationProcessingFilter에 전달한다.

  • (6)의 attemptAuthentication() 메서드는 클라이언트에서 전달한 username과 password 정보를 이용해 인증을 시도한다.
    attemptAuthentication() 메서드는 상위 클래스인 AbstractAuthenticationProcessingFilter의 doFilter() 메서드에서 호출되는데, Filter에서 어떤 처리를 하는 시작점은 doFilter()이다.
    (6-1)은 HTTP Method가 POST가 아니면 Exception을 throw 한다.
    (6-2)에서는 클라이언트에서 전달한 username과 password 정보를 이용해 UsernamePasswordAuthenticationToken을 생성한다.
    ⭐ 여기서의 UsernamePasswordAuthenticationToken은 인증을 하기 위해 필요한 인증 토큰이다. 인증에 성공한 인증 토큰과는 상관이 없다.
    (6-3)에서 AuthenticationManager의 authenticate() 메서드를 호출해 인증 처리를 위임하는 것을 볼 수 있다.

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 클래스는 UsernamePasswordAuthenticationFilter가 상속하는 상위 클래스로써 Spring Security에서 제공하는 Filter 중 하나이다.

AbstractAuthenticationProcessingFilter 클래스의 역할
⭐ AbstractAuthenticationProcessingFilter는 HTTP 기반의 인증 요청을 처리하지만, 실질적인 인증 시도는 하위 클래스에 맡기고 인증에 성공하면 인증된 사용자의 정보를 SecurityContext에 저장한다.

// 코드 생략

(1)을 통해 AbstractAuthenticationProcessingFilter 클래스가 Spring Security의 Filter임을 알 수 있다.

(1-1)에서는 AbstractAuthenticationProcessingFilter 클래스가 인증 처리를 해야되는지, 아니면 다음 Filter를 호출할지 여부를 결정하한다.
(1-1)에서 호출하는 requiresAuthentication() 메서드는 하위 클래스에서 전달받은 requiresAuthenticationRequestMatcher 객체를 통해 들어오는 요청이 인증 처리를 해야 되는지 여부를 결정한다.

AntPathRequestMatcher("/login","POST")의 파라미터인 URL과 HTTP Method가 매칭 조건이 된다.

(1-2)에서는 하위 클래스에 인증을 시도해 줄 것을 요청한다. 여기서의 하위 클래스는 UsernamePasswordAuthenticationFilter가 된다.

(1-3)에서는 인증에 성공하면 처리할 동작을 수행하기 위해 successfulAuthentication() 메서드를 호출한다.
successfulAuthentication() 메서드는 (3)에서 확인할 수 있는데, 인증에 성공한 이후 ⭐ SecurityContextHolder를 통해 사용자의 인증 정보를 SecurityContext에 저장한 뒤, SecurityContext를 HttpSession에 저장한다.
만약 인증에 실패하면, (1-4)와 같이 unsuccessfulAuthentication() 메서드를 호출하여 인증 실패 시 처리할 동작을 수행한다.

unsuccessfulAuthentication() 메서드는 (4)에서 확인할 수 있다시피 SeucurityContext를 초기화 하고, AuthenticationFailureHandler를 호출합니다.

🔑Key Summary

  • UsernamePasswordAuthenticationFilter는 클라이언트로부터 전달 받은 Username과 Password를 Spring Security가 인증 프로세스에서 이용할 수 있도록 UsernamePasswordAuthenticationToken을 생성한다.

  • AbstractAuthenticationProcessingFilter는 HTTP 기반의 인증 요청을 처리하지만, 실질적인 인증 시도는 하위 클래스에 맡기고, 인증에 성공하면 인증된 사용자의 정보를 SecurityContext에 저장하는 역할을 한다.

  • Authentication은 Spring Security에서의 인증 자체를 표현하는 인터페이스이다.

  • AuthenticationManager는 이름 그대로 인증 처리를 총괄하는 매니저 역할을 하는 인터페이스이며, 인증을 위한 실질적인 관리는 AuthenticationManager를 구현하는 구현 클래스를 통해 이루어진다.

  • ProviderManager는 이름에서 유추할 수 있듯이 AuthenticationProvider를 관리하고, AuthenticationProvider에게 인증 처리를 위임하는 역할을 한다.

  • AuthenticationProvider는 AuthenticationManager로부터 인증 처리를 위임 받아 실질적인 인증 수행을 담당하는 컴포넌트이다.

  • UserDetails는 데이터베이스 등의 저장소에 저장된 사용자의 Username과 사용자의 자격을 증명해주는 크리덴셜(Credential)인 Password, 그리고 사용자의 권한 정보를 포함하는 컴포넌트이며, AuthenticationProviderUserDetails를 이용해 자격 증명을 수행한다.

  • UserDetailsService는 UserDetails를 로드(load)하는 핵심 인터페이스이다.

  • SecurityContext는 인증된 Authentication 객체를 저장하는 컴포넌트이고, SecurityContextHolder는 SecurityContext를 관리하는 역할을 담당한다.


참고 자료

📄 Servlet Authentication Architecture

0개의 댓글