본문 바로가기
spring/spring security

[Spring Security] DaoAuthenticationProvider

by moonsiri 2023. 5. 9.
728x90
반응형

org.springframework.security.authentication.dao.DaoAuthenticationProvider

 

DaoAuthenticationProvider는 UserDetailsService 및 PasswordEncoder를 사용하여 사용자 아이디와 암호를 인증하는 AuthenticationProvider 구현입니다.

 

  1. The authentication Filter는 ProviderManager에 의해 구현되는 AuthenticationManager에 UsernamePasswordAuthenticationToken을 전달합니다.
  2. ProviderManager는 DaoAuthenticationProvider 타입의 AuthenticationProvider를 사용하도록 구성됩니다.
  3. DaoAuthenticationProvider는 UserDetailsService에서 UserDetails를 조회합니다.
  4. DaoAuthenticationProvider는 PasswordEncoder를 사용하여 이전 단계에서 반환된 UserDetails의 암호를 확인합니다.
  5. 인증에 성공하면 반환되는 인증은 UsernamePasswordAuthenticationToken 타입이며 구성된 UserDetailsService에서 반환된 UserDetails인 주체를 가집니다. 마지막으로 반환된 UsernamePasswordAuthenticationToken은 인증 필터에 의해 SecurityContextHolder에 설정됩니다.

 

 

DaoAuthenticationProvider가 상속 중인 AbstractUserDetailsAuthenticationProvider 구현체의 authenticate 메서드를 확인해보겠습니다.

	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication); // userDetailsService
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

 

인증 메서드를 확인 해 보면 다음과 같은 순서로 진행됩니다.

  1. 유저 정보 조회
    • UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  2. 인증 전처리
    • preAuthenticationChecks.check(user);
  3. 인증
    • additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
  4. 인증 후처리
    • postAuthenticationChecks.check(user);
  5. 인증토큰 생성
    • return createSuccessAuthentication(principalToReturn, authentication, user);

 

 

DaoAuthenticationProvider를 재정의하여 커스텀할 수 있습니다.

	@Bean
	public AuthenticationProvider daoAuthenticationProvider(SecurityDetailsService userDetailsService) {

		CustomAuthenticationProvider authenticationProvider = new CustomAuthenticationProvider();
		authenticationProvider.setUserDetailsService(userDetailsService);
		authenticationProvider.setHideUserNotFoundExceptions(false);
		authenticationProvider.setPreAuthenticationChecks(new CustomPreAuthenticationChecks());
		authenticationProvider.setPostAuthenticationChecks(new CustomPostAuthenticationChecks());
		// authenticationProvider.setPasswordEncoder(passwordEncoder());

		return authenticationProvider;
	}
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {

	@Override
	public void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		String username = userDetails.getUsername();
		String password = (String)authentication.getCredentials();
		
		// authentication
	}

}
@Service
public class SecurityDetailsService implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String empIdInfo) throws UsernameNotFoundException {
    	
		// 유저 정보 조회
        
		return userDetails;
    }

}
public class CustomPreAuthenticationProvider implements UserDetailsChecker {

	@Override
	public void check(UserDetails toCheck) {
		// 사용자 잠금 상태, 사용여부, 만료 체크 등 (ex. DefaultPreAuthenticationChecks)
	}
}
public class CustomPostAuthenticationProvider implements UserDetailsChecker {

	@Override
	public void check(UserDetails toCheck) {
		// 사용자 자격 증명(암호) 만료 여부 체크 등 (ex. DefaultPostAuthenticationChecks)
	}
}

 

passwordEncoder

 

[Spring Security] DelegatingPasswordEncoder와 BCryptPasswordEncoder strength에 따른 수행시간

DelegatingPasswordEncoder에 대해 정리하기 전에, 스프링 시큐리티 래퍼런스 내용을 살펴보겠습니다. Srping Password Storage Spring Security’s PasswordEncoder interface is used to perform a one way transformation of a password to

moonsiri.tistory.com

 

 

로그인 성공 후 SecurityContextHolder에 유저 정보를 추가합니다.

public static void setAuthentication(Authentication authentication, SecurityMemberInfoVO memberInfo) {
	Authentication newAuth = new UsernamePasswordAuthenticationToken(memberInfo, authentication.getCredentials(), memberInfo.getAuthorities());
	SecurityContextHolder.getContext().setAuthentication(newAuth);
}

 

The Authentication is set on the SecurityContextHolder. Later, if you need to save the SecurityContext so that it can be automatically set on future requests, SecurityContextRepository#saveContext must be explicitly invoked. See the SecurityContextHolderFilter class. (SessionRepositoryFilter)

어디서든 SecurityContextHolder에 authentication을 저장을 하면 자동으로 세션에 저장합니다. (SessionRepositoryFilter)

 

 

[Reference]

https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/dao-authentication-provider.html

https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html

728x90
반응형

댓글