UserDetailsService
사용자 인증을 처리하는 핵심 인터페이스 중 하나로, 사용자의 정보를 불러와 인증을 위한 로직을 처리하는 역할을 한다. 주로 사용자 데이터를 데이터베이스에서 조회하고, 인증 객체로 반환하는 데 사용된다.
UserDetailsService의 주요 기능:
- 사용자 조회: UserDetailsService는 사용자의 이름(주로 사용자 ID, username)을 입력받아 해당 사용자의 세부 정보를 불러오는 책임을 가진다.
- 인증 처리: 불러온 사용자 정보는 Spring Security에 의해 인증 절차에 사용된다.
- UserDetails 반환: 사용자의 세부 정보는 UserDetails라는 인터페이스로 반환된다. 이 객체는 인증에 필요한 정보를 담고 있다(사용자 이름, 비밀번호, 권한 등).
UserDetails 인터페이스
loadUserByUsername 메서드가 반환하는 UserDetails 객체는 다음과 같은 사용자 정보를 포함한다:
- getUsername(): 사용자의 이름을 반환한다.
- getPassword(): 사용자의 암호화된 비밀번호를 반환한다.
- getAuthorities(): 사용자가 가진 권한(역할)을 반환한다.
- isAccountNonExpired(): 계정이 만료되지 않았는지 여부를 반환한다.
- isAccountNonLocked(): 계정이 잠기지 않았는지 여부를 반환한다.
- isCredentialsNonExpired(): 사용자 자격 증명이 만료되지 않았는지 여부를 반환한다.
- isEnabled(): 계정이 활성화 상태인지 여부를 반환한다.
코드 설명
@Service("userDetailsService") // Spring Bean으로 등록, "userDetailsService" 이름으로 스프링 컨텍스트에서 참조 가능
@RequiredArgsConstructor // final로 선언된 필드의 생성자를 자동으로 생성해주는 Lombok 어노테이션
public class FormUserDetailsService implements UserDetailsService { // Spring Security의 UserDetailsService 인터페이스를 구현하여 사용자 인증 처리
private final AccountRepository accountRepository; // 사용자 정보를 데이터베이스에서 조회하는 AccountRepository 의존성 주입
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // username으로 사용자를 조회하고 UserDetails 반환
Account account = accountRepository.findByUserName(username); // 데이터베이스에서 사용자를 조회 (AccountRepository 사용)
if(account == null){ // 사용자가 데이터베이스에 없을 경우 예외 처리
throw new UsernameNotFoundException("No user found with username" + username); // 사용자 이름으로 사용자를 찾을 수 없으면 예외 발생
}
// Account 엔티티에서 정보를 가져와 AccountDto 객체를 생성
AccountDto accountDto = AccountDto.builder()
.id(account.getId()) // 사용자의 ID 설정
.userName(account.getUserName()) // 사용자의 이름 설정
.password(account.getPassword()) // 사용자의 암호화된 비밀번호 설정
.roles(account.getRoles()) // 사용자의 역할 (권한) 설정
.build();
// 사용자 권한(roles)을 SimpleGrantedAuthority 객체로 변환하여 리스트로 생성
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(account.getRoles()));
// AccountContext를 UserDetails로 반환. AccountContext는 UserDetails 인터페이스를 구현한 사용자 세부 정보
return new AccountContext(accountDto, authorities);
}
}
AuthenticationProvider
1. 폼 기반 인증 흐름
Spring Security에서의 폼 기반 인증은 보통 다음과 같은 과정을 거친다:
- 사용자가 로그인 페이지에서 사용자 이름과 비밀번호를 입력한다.
- 서버로 폼 데이터가 제출되면, **UsernamePasswordAuthenticationFilter*가 요청을 가로채서 인증 시도를 한다.
- UsernamePasswordAuthenticationFilter는 사용자가 입력한 username과 password를 **AuthenticationManager*에 전달한다.
- AuthenticationManager는 등록된 여러 AuthenticationProvider 중에서 적절한 제공자에게 인증을 위임한다.
- 일반적으로 **DaoAuthenticationProvider*가 폼 인증을 처리하고, 이를 통해 사용자 정보와 비밀번호를 검증한다.
2. DaoAuthenticationProvider의 역할
DaoAuthenticationProvider는 폼 인증에서 실제로 사용자의 비밀번호와 사용자 정보를 검증하는 AuthenticationProvider의 구현체다. 주로 UserDetailsService와 비밀번호 인코더를 사용하여 인증을 처리한다.
DaoAuthenticationProvider의 주요 구성 요소:
- UserDetailsService:
- UserDetailsService는 사용자의 이름(주로 username)을 입력받아, 사용자의 세부 정보를 조회하는 인터페이스다.
- 이 인터페이스는 사용자 정보를 데이터베이스에서 조회하거나 외부 API로부터 불러오는 역할을 한다.
- PasswordEncoder:
- PasswordEncoder는 저장된 비밀번호(해시된 값)와 사용자가 입력한 비밀번호를 비교하는 기능을 제공한다.
- 일반적으로 BCryptPasswordEncoder와 같은 해시 알고리즘을 사용해 비밀번호를 인코딩하고 검증한다.
3. 폼 기반 인증 처리 과정
DaoAuthenticationProvider가 폼 인증을 처리하는 기본적인 과정은 다음과 같다:
- 사용자가 입력한 username을 통해 UserDetailsService를 사용하여 데이터베이스에서 사용자 정보를 조회한다.
- 조회된 사용자 정보에서 비밀번호를 가져온다.
- 사용자가 입력한 비밀번호와 저장된 비밀번호를 **PasswordEncoder*로 비교한다.
- 비밀번호가 일치하면 인증이 성공하고, Authentication 객체가 반환된다.
- 인증이 성공하면 세션 또는 JWT 토큰을 생성하여 사용자에게 부여한다.
코드 설명
@Component("authenticationProvider") // 이 클래스를 Spring의 Bean으로 등록, "authenticationProvider" 이름으로 사용 가능
@RequiredArgsConstructor // Lombok 어노테이션으로, final로 선언된 필드의 생성자를 자동으로 생성
public class FormAuthenticationProvider implements AuthenticationProvider { // Spring Security의 AuthenticationProvider 인터페이스를 구현하여 인증 처리
private final UserDetailsService userDetailsService; // 사용자 정보를 로드하는 UserDetailsService 의존성 주입
private final PasswordEncoder passwordEncoder; // 비밀번호 검증을 위한 PasswordEncoder 의존성 주입
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 사용자가 입력한 로그인 ID를 가져옴
String loginId = authentication.getName();
// 사용자가 입력한 비밀번호를 가져옴
String password = (String) authentication.getCredentials();
// 입력된 loginId를 통해 사용자 정보를 로드 (AccountContext를 통해 사용자 정보와 권한 정보 포함)
AccountContext accountContext = (AccountContext) userDetailsService.loadUserByUsername(loginId);
// 입력된 비밀번호와 저장된 비밀번호를 PasswordEncoder로 비교 (일치하지 않으면 예외 발생)
if (passwordEncoder.matches(password, accountContext.getPassword())) {
throw new BadCredentialsException("Invalid password"); // 비밀번호가 틀리면 인증 실패 예외 발생
}
// 인증 성공 시 UsernamePasswordAuthenticationToken 객체를 생성하여 반환
// 여기서 accountContext의 DTO와 권한 정보를 포함하여 인증 토큰 생성
return new UsernamePasswordAuthenticationToken(accountContext.getAccountDto(), null, accountContext.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// 이 AuthenticationProvider가 지원하는 토큰의 타입을 명시 (UsernamePasswordAuthenticationToken 타입을 지원)
return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
}
}
'SPRING > SpringSecurity' 카테고리의 다른 글
[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] Rest 인증 보안 및 화면 구성 (0) | 2024.10.16 |
---|---|
[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] AuthenticationSuccess(Failure)Handler, AccessDeniedHandler (0) | 2024.10.15 |
[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] 인증상세 (0) | 2024.10.15 |
[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] 로그인, 회원가입 (1) | 2024.10.13 |
[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] 프로젝트 환경 설정 (1) | 2024.10.13 |