Spring Security 동작 원리 (2)

허진혁·2023년 5월 24일
0

이전 포스팅에서 이어지는 내용이에요.

Spring Seucurity 동작 과정

AuthenticationProvider 인터페이스를 구현하는 클래스 생성

@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
    private final CustomUserDetailsService customUserDetailsService;
    private final PasswordEncoder passwordEncoder;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        
        UserDetails loadedUser = customUserDetailsService.loadUserByUsername(username);
        
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(loadedUser, null, loadedUser.getAuthorities());
        result.setDetails(authentication.getDetails());
        return result;
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
  • 위 부분은 필요에 의한 것만 가져온 코드이며 실제로는 예외처리 등의 로직 이 있어요.

실질적인 인증 로직을 위해 AuthenticationProvider interface를 구현한 클래스가 필요한데요.

AuthenticationProvider를 implements 하게 되면 authenticate(), supports() 메서드를 구현하게 되요.

UsernamePasswordAuthenticationToken 클래스

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
	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); // must use super, as we override
	}
}

두 개의 생성자를 통해 파악할 수 있는 것이 있어요. 첫번째 생성자에서 setAuthenticated(false)를 보면 인증전의 생성자를 의미하고, 두번째 생성자는 setAuthenticated(ture)를 보아 인증이 성공한 객체 생성자를 의미해요.


세부 구현

CustomUserDetailsService

@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("not found"));
    }
}

SecurityConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final CustomAuthenticationProvider authProvider;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider);
    }
}

인증 로직이 구현된 CustomAuthenticationProvider를 ProviderManager가 알 수 있도록 ProviderManager에 등록해줘야 해요.

시큐리티 설정을 위한 SecurityConfig 클래스에서 위와 같이 authenticationProvider를 추가해주면 끝나요.


이렇게 구현된 인증 절차를 통해 인증이 완료되면 Authentication을 SecurityContextHolder 객체 안의 SecurityContext에 저장합니다.

Authentication authentication = SecutiryContextHolder.getContext().getAuthentication();

그리고 이러한 방식으로 저장된 인증 객체를 전역적으로 사용하면 되요.

SecutiryContextHolder

SecurityContext 객체를 저장하고 감싸고 있는 wrapper 클래스로 SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 콘텍스트에 대한 세부 정보가 저장되는 곳이에요.

SecutiryContext

Authentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있습니다. ThreadLocal에 저장되어 아무 곳에서나 참조가 가능하도록 설계되어 있어요.


총 정리

  1. 요청 수신
  • 사용자가 form을 통해 로그인 정보가 담긴 Request를 보냄.
  1. 토큰 생성
  • AuthenticationFilter가 요청을 받아서 UsernamePasswordAuthenticationToken토큰(인증용 객체)을 생성
    UsernamePasswordAuthenticationToken은 해당 요청을 처리할 수 있는 Provider을 찾는데 사용
  1. AuthenticationFilter로 부터 인증용 객체를 전달 받는다.
  • Authentication Manager 에게 처리 위임
  • Authentication Manager는 List형태로 Provider들을 갖고 있음
  1. Token을 처리할 수 있는 Authentication Provider 선택한다.
  • 실제 인증을 할 AuthenticationProvider에게 인증용 객체를 다시 전달
  1. 인증 절차
  • 인증 절차가 시작되면 AuthenticationProvider 인터페이스가 실행되고 DB에 있는 사용자의 정보와 화면에서 입력한 로그인 정보를 비교
  1. UserDetailsService의 loadUserByUsername 메소드 수행한다.
  • AuthenticationProvider 인터페이스에서는 authenticate() 메소드를 오버라이딩 하게 되는데 이 메소드의 파라미터인 인증용 객체로 화면에서 입력한 로그인 정보를 가져옴
  1. AuthenticationProvider 인터페이스에서 DB에 있는 사용자의 정보를 가져오려면, UserDetailsService 인터페이스를 사용한다.

  2. UserDetailsService 인터페이스는 화면에서 입력한 사용자의 username으로 loadUserByUsername() 메소드를 호출하여 DB에 있는 사용자의 정보를 UserDetails 형태로 가져옴. 만약 사용자가 존재하지 않으면 예외를 던짐(UsernameNotFound). 이렇게 DB에서 가져온 이용자의 정보와 화면에서 입력한 로그인 정보를 비교하게 되고, 일치하면 Authentication 참조를 리턴하고, 일치 하지 않으면 예외를 던짐.

  3. 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행한다.(실패시 AuthenticationFailureHandler를 실행한)

profile
Don't ever say it's over if I'm breathing

0개의 댓글