[Spring Boot] Spring Security JWT 토큰 로그인 구현 - 1에 이어서 로그인 요청 시 인증 수행 과정을 구현하자.
CustomDetailsService 구현
// Spring Security의 UserDetailsService
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
// 비밀번호를 추가로 받는 CustomDetailsService
public interface CustomDetailsSerivce {
UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException;
}
먼저 Spring Security의 UserDetailsService의 loadUserByUsername 메소드는 username만을 파라미터로 받기 때문에 추가로 password를 파라미터로 받는 CustomDetailsService 인터페이스를 만든다.
@Component
@RequiredArgsConstructor
public class CustomUserDetailsService implements CustomDetailsSerivce {
private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username, String password) {
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("ID를 입력해주세요.");
}
if (StringUtils.isEmpty(password)) {
throw new BadCredentialsException("Password를 입력해주세요.");
}
Member member = memberRepository.findByUserId(username)
.orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 Member입니다."));
if (!passwordEncoder.matches(password, member.getPassword())) {
throw new BadCredentialsException("Password가 일치하지 않습니다.");
}
// 임시로 USER 권한 부여
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
User user = new User(
member.getUserName(),
"",
grantedAuthorities
);
return user;
}
Spring Security Config에 Bean으로 등록한 패스워드인코더와 사용자 정보를 저장하는 Member 테이블에 접근하는 MemberRepository 의존성을 주입받는다.
username, password 입력에 대한 유효성과 패스워드 일치 유효성 검증 후 성공 시 임시로 ROLE_USER 권한을 부여한 UserDetails 객체롤 리턴한다.
유효성 검증 실패 시 throw하는 UsernameNotFoundException, BadCredentialsException는 AuthenticationException을 상속받는 클래스들로 1편에서 구현했던 JwtAuthenticationEntryPoint에서 예외 핸들링을 하게 된다.
CustomAuthenticationProvider 구현
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomDetailsSerivce customDetailsSerivce;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UserDetails userDetails = customDetailsSerivce.loadUserByUsername((String) authentication.getPrincipal(), (String) authentication.getCredentials());
return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
AuthenticationManager로부터 인증을 위임받는 AuthenticationProvider를 구현하여 CustomUserDetailsService의 loadByUsername 메소드를 수행하고 인증 객체를 리턴한다.
LoginService 구현
@Getter
public class LoginDto {
private String userId;
private String password;
}
userId와 password가 담긴 LoginDto를 파라미터로 받는 LoginService를 구현한다.
@Service
@RequiredArgsConstructor
public class LoginService {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final TokenProvider tokenProvider;
public TokenDto login(LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUserId(), loginDto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
TokenDto tokenInfo = tokenProvider.generateToken(authentication);
return tokenInfo;
}
}
파라미터로 받은 LoginDto로 UsernamePasswordAuthenticationToken 객체를 생성해 authenticationManager의 인증을 수행한다. authenticationManager은 방금 구현한 CustomAuthenticationProvider에게 인증을 위임하고 인증 성공 시 SecurityContextHolder에 인증 정보를 저장하고 액세스 토큰을 발급하여 리턴한다.

Login API 호출하여 인증 성공 시 accessToken과 refreshToken을 응답으로 받는 것을 확인할 수 있다.
끝.
'Spring Boot' 카테고리의 다른 글
| [Spring Boot] @RestControllerAdvice를 사용한 공통 응답 처리 (0) | 2023.11.26 |
|---|---|
| [SpringBoot] 공통 응답 DTO (0) | 2023.11.10 |
| [Spring Boot] Spring Security JWT 토큰 로그인 구현 - 1 (0) | 2023.10.29 |