org.springframework.security.authentication.dao.DaoAuthenticationProvider
DaoAuthenticationProvider는 UserDetailsService 및 PasswordEncoder를 사용하여 사용자 아이디와 암호를 인증하는 AuthenticationProvider 구현입니다.
- The authentication Filter는 ProviderManager에 의해 구현되는 AuthenticationManager에 UsernamePasswordAuthenticationToken을 전달합니다.
- ProviderManager는 DaoAuthenticationProvider 타입의 AuthenticationProvider를 사용하도록 구성됩니다.
- DaoAuthenticationProvider는 UserDetailsService에서 UserDetails를 조회합니다.
- DaoAuthenticationProvider는 PasswordEncoder를 사용하여 이전 단계에서 반환된 UserDetails의 암호를 확인합니다.
- 인증에 성공하면 반환되는 인증은 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);
}
인증 메서드를 확인 해 보면 다음과 같은 순서로 진행됩니다.
- 유저 정보 조회
- UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
- 인증 전처리
- preAuthenticationChecks.check(user);
- 인증
- additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
- 인증 후처리
- postAuthenticationChecks.check(user);
- 인증토큰 생성
- 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
로그인 성공 후 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/architecture.html
'spring > spring security' 카테고리의 다른 글
[Spring Security5] OAuth2 로그인 설정 (0) | 2024.10.17 |
---|---|
Spring Security 5에서 Spring Security 6으로 변경 (URL-권한 인가) (0) | 2024.04.30 |
[Spring Security] DelegatingPasswordEncoder와 BCryptPasswordEncoder strength에 따른 수행시간 (0) | 2022.03.20 |
[Spring Security5] 권한 계층구조(roleHierarchy) 설정 (0) | 2021.08.13 |
[Spring Security] redis 세션에서 SecurityContext 수정 (0) | 2021.07.12 |
댓글