첫 번째 포스팅에서 이어지는 내용입니다. 참고 부탁드리겠습니다.
AuthenticationProvider 인터페이스를 구현하는 클래스 만들기
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService customUserDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails loadedUser = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(loadedUser, null, loadedUser.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
실질적인 인증 로직을 위해 AuthenticationProvider interface를 구현한 클래스가 필요한데요.
AuthenticationProvider를 implements 하게 되면 authenticate(), supports() 메서드를 구현하게 됩니다.
* 위 구조는 보기 쉽도록 정말 필수적인 부분만 넣은 것이고 실제로는 예외처리 등의 로직이 더 들어가게 됩니다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
UsernamePasswordAuthenticationToken Class 입니다.
첫 번째 생성자는 인증 전의 객체를 생성하고, 두 번째 생성자는 인증이 완료된 객체를 생성합니다.
세부적인 구현
@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("not found"));
}
}
CustomUserDetailsService
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
UserRepository
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationProvider authProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
}
인증 로직이 구현된 CustomAuthenticationProvider를 ProviderManager가 알 수 있도록 ProviderManager에 등록해줘야 합니다.
시큐리티 설정을 위한 SecurityConfig 클래스에서 위와 같이 authenticationProvider를 추가해주면 됩니다.
이렇게 구현된 인증 절차를 통해 인증이 완료되면 Authentication을 SecurityContextHolder 객체 안의 SecurityContext에 저장합니다.
Authentication authentication = SecutiryContextHolder.getContext().getAuthentication();
그리고 이러한 방식으로 저장된 인증 객체를 전역적으로 사용할 수 있게 됩니다.
- SecutiryContextHolder
SecurityContext 객체를 저장하고 감싸고 있는 wrapper 클래스로 SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 콘텍스트에 대한 세부 정보가 저장됩니다.
(MODE_THREADLOCAL, MODE_INHERITABLETHREADLOCAL, MODE_GLOBAL 세 가지 저장 방식이 있습니다.)
- SecutiryContext
Authentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있습니다. ThreadLocal에 저장되어 아무 곳에서나 참조가 가능하도록 설계되어 있습니다.
참고자료
https://jeong-pro.tistory.com/205
https://mangkyu.tistory.com/76s
https://springbootdev.com/2017/08/23/spring-security-authentication-architecture/
'Programming > Spring Boot' 카테고리의 다른 글
spring security + JWT 로그인 기능 파헤치기 - 1 (9) | 2021.09.22 |
---|---|
UsernameNotFoundException Not Working 이유 파헤치기 (0) | 2021.09.20 |
Spring Security 시큐리티 동작 원리 이해하기 - 1 (0) | 2021.09.09 |
관습적인 추상화 Service, ServiceImpl 구조를 사용해야 할까? (12) | 2021.09.08 |
IntelliJ 인텔리제이 properties 파일 한글 깨짐 설정 방법 (0) | 2021.09.04 |