2024. 4. 5. 21:43ㆍProject/2023-02 캡스톤디자인
이번 포스팅에서는 인증(Authentication)에 관련된 클래스들을 정의하고 구현해보려고 한다.
먼저 인증 작업을 처리하기 위해 사용되는 AuthenticationFilter 클래스를 먼저 정의하였다.
1) CustomAuthenticationFilter 클래스 생성
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest;
try {
authRequest = getAuthRequest(request);
setDetails(request, authRequest);
} catch (Exception e) {
throw new ProfileApplicationException(ErrorCode.BUSINESS_EXCEPTION_ERROR);
}
// Authentication 객체를 반환한다.
return this.getAuthenticationManager().authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) throws Exception {
try {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
UserLoginDTO user = objectMapper.readValue(request.getInputStream(), UserLoginDTO.class);
return new UsernamePasswordAuthenticationToken(user.getEmail(), user.getUserPw());
} catch (UsernameNotFoundException e) {
throw new UsernameNotFoundException(e.getMessage());
} catch (Exception e) {
throw new ProfileApplicationException(ErrorCode.IO_ERROR);
}
}
}
먼저 CustomAuthenticationFilter 클래스는 UsernamePasswordAuthenticationFilter 클래스를 상속하는 클래스이며, 사용자 정의 AuthenticationFilter를 구현하는 역할을 수행한다.
- 생성자 - 여기서는 UsernamePasswordAuthenticationFilter의 AuthenticationManager를 생성자를 통해 상속받은 후 사용할 수 있도록 설정한다.
- attemptAuthentication: 이 메서드는 사용자가 로그인("**/login")을 시도할 때 호출된다. 메서드 내에서는 HTTP 요청으로부터 사용자의 자격 증명(이메일과 비밀번호)을 추출하여 UsernamePasswordAuthenticationToken 객체를 생성한다. 해당 토큰은 AuthenticationManager에 전달되어 인증 과정이 수행된다. 인증이 성공하면 인증된 사용자 정보를 담은 Authentication 객체가 반환되고, 실패하면 AuthenticationException이 발생한다. attemptAuthentication메서드에서 예외가 발생하거나 AuthenticationException이 발생하면 Spring Security에 의해 잡히게 되고, CustomAuthFailureHandler로 전달된다.
- getAuthRequest: HTTP 요청으로부터 사용자의 자격 증명을 추출하여 UsernamePasswordAuthenticationToken 객체를 생성한다. Jackson 라이브러리의 ObjectMapper를 사용하여 HTTP 요청 본문에서 JSON 형태의 사용자 정보를 읽어들이고, 이를 바탕으로 UserLoginDto 객체를 생성한 다음, 해당 정보를 사용하여 토큰을 생성하게 된다.
여기서 UsernamePasswordAuthenticationFilter는 Spring Security에서 제공하는 필터로, 사용자의 사용자 이름과 비밀번호를 처리하는 데 사용된다. 이 필터는 HTTP 요청에서 사용자 이름과 비밀번호를 추출하고, 이를 사용하여 인증 과정을 처리한다. 기능을 요약하면 다음과 같다.
- 인증 요청 처리: 보통 /login 경로로 들어오는 POST 요청을 처리한다. 사용자 이름과 비밀번호 입력 후 로그인 API를 호출하면, 해당 필터가 요청을 가로채서 사용자 이름과 비밀번호를 추출한다.
- UsernamePasswordAuthenticationToken 생성: 필터는 추출한 사용자 이름과 비밀번호를 사용하여 UsernamePasswordAuthenticationToken 객체를 생성한다. 이 토큰은 인증 객체로 사용되며, 이후 인증 과정에서 AuthenticationManager에게 전달된다.
- AuthenticationManager 호출: 필터는 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 전달한다. AuthenticationManager는 등록된 AuthenticationProvider를 사용하여 사용자를 인증하게 된다.
- 인증 성공 및 실패 처리: 인증이 성공하면, 필터는 사용자를 원래 요청한 페이지로 리디렉션 처리한다. 인증이 실패하면, 사용자는 일반적으로 오류 메시지를 반환한다.
2) CustomAuthenticationProvider 클래스 생성
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
// AuthenticationFilter에서 생성된 토큰으로부터 ID, PW를 조회
String loginId = token.getName();
String userPassword = (String) token.getCredentials();
// UserDetailsService를 통해 DB에서 username으로 사용자 조회
SecurityUserDetailsDto securityUserDetailsDto = (SecurityUserDetailsDto) userDetailsService.loadUserByUsername(loginId);
// 대소문자를 구분하는 matches() 메서드로 db와 사용자가 제출한 비밀번호를 비교
if (!bCryptPasswordEncoder().matches(userPassword, securityUserDetailsDto.getUserDto().password())) {
throw new BadCredentialsException(securityUserDetailsDto.getUsername() + "Invalid password");
}
// 인증이 성공하면 인증된 사용자의 정보와 권한을 담은 새로운 UsernamePasswordAuthenticationToken을 반환한다.
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(securityUserDetailsDto, userPassword, securityUserDetailsDto.getAuthorities());
return authToken;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
CustomAuthenticationProvider 클래스는 AuthenticationProvider 인터페이스를 구현하여 사용자 정의 인증 로직을 제공한다. Spring Security에서는 해당 인터페이스를 사용하여 사용자 인증을 하게 되며, 여기서는 직접 커스텀해서 사용하기 위해 CustomAuthenticationProvider로 생성하여 정의하였다.
authenticate 메서드는 다음과 같은 역할을 수행한다
- CustomUserDetailsService를 사용하여 데이터베이스에서 사용자 정보를 조회한다. 이는 사용자의 세부 정보를 로드하는 역할을 수행한다. 여기서는 loadUserByUsername 메서드를 통해 사용자 username(loginId)에 해당하는 사용자 정보를 SecurityUserDetailsDto 객체로 받아온다.
- 패스워드 검증: 사용자가 입력한 비밀번호와 데이터베이스에 저장된 비밀번호를 비교한다. 이를 위해 BCryptPasswordEncoder를 사용하여 입력된 비밀번호를 암호화하고, 이 암호화된 비밀번호가 데이터베이스에 저장된 암호화된 비밀번호와 일치하는지 확인하는 작업을 수행한다.
- 인증 토큰 생성: 인증이 성공하면, 사용자 정보와 권한 정보를 포함한 새로운 UsernamePasswordAuthenticationToken 객체를 생성하고 반환한다. 이 토큰은 Spring Security 컨텍스트 내에서 사용자가 인증된 상태임을 나타낸다.
supports 메서드는 CustomAuthenticationProvider가 특정 인증 타입을 지원하는지 여부를 반환한다. 여기서는 UsernamePasswordAuthenticationToken 타입을 지원한다고 명시되어 있다. 이는 CustomAuthenticationProvider가 UsernamePasswordAuthenticationToken 처리에 사용할 수 있음을 의미한다.
3) CustomUserDetailsService 생성
@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// 1. userRepository로부터 email로 유저정보를 받아온다.
User user = userRepository.findByEmail(email)
.orElseThrow(
() -> new ProfileApplicationException(ErrorCode.USER_NOT_FOUND)
);
// 2.user를 dto로 변환시켜준다.
UserDto userDto = UserDto.fromEntity(user);
// 3. 사용자 정보를 기반으로 SecurityUserDetailsDto 객체를 생성한다.
return new SecurityUserDetailsDto(
userDto,
Collections.singleton(new SimpleGrantedAuthority(
userDto.role().toString()
))
);
}
}
CustomUserDetailsService 클래스는 Spring Security의 UserDetailsService 인터페이스의 구현체로서 사용자 정의 사용자 상세 정보 관련 서비스를 제공한다.
loadUserByUsername 메서드는 사용자 이름(여기서는 이메일)을 인자로 받아 해당 사용자의 상세 정보를 읽어온다. 정리하자면, 다음과 같은 역할을 수행한다.
- 사용자 정보 조회: 먼저, 주어진 이메일을 기반으로 userRepository를 사용하여 데이터베이스에서 사용자 정보를 조회한다. 만약 해당 이메일을 가진 사용자가 없다면 ProfileApplicationException 예외를 발생시킨다.
- SecurityUserDetailsDto 생성: 최종적으로, UserDto와 사용자의 권한 정보를 포함하여 SecurityUserDetailsDto 객체를 생성한다. SecurityUserDetailsDto는 UserDetails 인터페이스를 구현하는 클래스로, Spring Security에서 사용자의 인증 정보를 관리하는 데 사용된다. 여기서 사용자의 권한은 SimpleGrantedAuthority 객체를 사용하여 구성된다.
여기까지 진행되면 인증(Authentication) 작업이 완료되며, 결과로 성공 또는 실패를 반환하게 된다.
다음 포스팅은 인증 작업이 성공했을 때, 호출되는 AuthenticationSuccessHandler와 인증작업이 실패했을 때 호출되는 AuthenticationFailureHandler를 각각 커스텀한 클래스들을 정의할 예정이다.
'Project > 2023-02 캡스톤디자인' 카테고리의 다른 글
[Spring Security] Filter 구현 (3) (0) | 2024.04.08 |
---|---|
[Spring Security] SpringBoot 3.1.x Redis 설정 (with JWT) (0) | 2024.04.08 |
[Spring Security] Filter 구현 (1) (0) | 2024.04.05 |
[Flask] ResNet활용 API 개발 (1) | 2024.03.07 |
Haversine 공식을 이용한 거리순 반환 로직 개발 (0) | 2024.03.07 |