AuthenticationProvider 커스텀하기
@Component
public class CustomUserAuthenticationProvider implements AuthenticationProvider { // (1)
private final CustomUserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
public HelloUserAuthenticationProvider(CustomUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
// ...생략
}
우선 AuthenticationProvider 인터페이스를 구현할 CustomUserAuthenticationProvider 클래스를 생성한다.
인증을 처리하기 위해 인증 정보를 조회하고 패스워드를 복호화해줄 때 사용할
기존에 만들어두었던 CustomUserDetailsService와 PasswordeEncoder 객체를 주입받는다.
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
private void verifyCredentials(Object credentials, String password) {
if (!passwordEncoder.matches((String)credentials, password)) {
throw new BadCredentialsException("Invalid User name or User Password");
}
}
우선 supports 메서드를 오버라이딩 하여
CustomUserAuthenticationProvider가 UsernamePasswordAuthentication 방식의
인증을 지원할 것이라고 스프링 시큐리티에 알려준다.
(해당 값이 트루면 해당 클래스의 authenticate 메서드 호출)
verifyCredentials 메서드는 이후에 사용할
데이터베이스의 패스워드와 크레덴셜의 패스워드를 비교하는 메서드다.
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication;
String username = authToken.getName();
Optional.ofNullable(username).orElseThrow(() -> new UsernameNotFoundException("Invalid User name or User Password"));
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String password = userDetails.getPassword();
verifyCredentials(authToken.getCredentials(), password);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return UsernamePasswordAuthenticationToken.authenticated(username, password, authorities);
}
authenticate 메서드는 커스텀한 인증 처리 로직에 따라 사용자 인증 여부를 결정하여
해당 메서드를 오버라이딩 하면 커스텀 인증 처리 로직을 적용할 수 있다.
UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) authentication;
String username = authToken.getName();
Optional.ofNullable(username).orElseThrow(() -> new UsernameNotFoundException("Invalid User name or User Password"));
파라미터로 받아온 authentication을 캐스팅하여 UsernamePasswordAuthenticationToken 객체를 얻어서
해당 객체에서 사용자의 Username을 얻어온다.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String password = userDetails.getPassword();
verifyCredentials(authToken.getCredentials(), password);
userDetailsService에서 Username과 일치하는 사용자 인증 정보를 가져온 후에
가져온 정보에서 패스워드를 가져와 로그인 정보의 패스워드와 비교한다.
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return UsernamePasswordAuthenticationToken.authenticated(username, password, authorities);
비밀번호까지 일치한다면 인증을 통과한 것이기에
해당 사용자의 권한을 생성한 후에 인증된 사용자의 정보를 리턴한다.
스프링 시큐리티의 예외 처리
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String password = userDetails.getPassword();
verifyCredentials(authToken.getCredentials(), password);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return UsernamePasswordAuthenticationToken.authenticated(username, password, authorities);
} catch (Exception ex) {
throw new UsernameNotFoundException(ex.getMessage());
스프링 시큐리티는 인증 실패 시 AuthenticationException 예외가 발생하지 않으면
예외에 대한 별도의 처리를 하지 않고 서블릿 컨테이너 측으로 넘기기 때문에
위의 코드처럼 기존의 인증 처리 로직을 트라이-캐치 문으로 감싼 후에
발생하는 모든 예외를 잡아서 AuthenticationException을 리턴하거나 상속 받는 예외를 리턴한다.
즉, 커스텀 인증을 구현할 때 발생하는 예외는
AuthenticationException을 되던지도록 코드를 짜야한다.
'Back-End > Security' 카테고리의 다른 글
[스프링 시큐리티] DelegatingPasswordEncoder (0) | 2023.07.11 |
---|---|
[스프링 시큐리티] 요청 처리 흐름 (0) | 2023.07.11 |
[스프링 시큐리티] 커스텀 인증 처리 구현 (0) | 2023.07.11 |
[스프링 시큐리티] 회원가입 구현 (0) | 2023.07.10 |
[스프링 시큐리티] 기본 (0) | 2023.07.10 |