Spring Security - UserDetails, UserDetailsService

2022. 7. 7. 16:04
반응형

UserDetails

  • 스프링 시큐리티가 사용자 정보를 알 수 있도록 사용자에 대한 정보를 저장하는 인터페이스이다.
  • 즉 스프링 시큐리티가 개발자 대신에 내부적으로 사용자 정보를 알 수 있도록 UserDetails 인터페이스를 상속받아서 스프링 시큐리티가 필요한 정보들을 구현해야한다.

다음과 같이 User 클래스에 UserDetails를 상속받아서 시큐리티가 필요한 정보를 구현할 수 있다. 이렇게 설정을 하면 스프링 시큐리티는 사용자를 시큐리티 정보에 구성하기 위한 최소한의 정보를 내부적으로 저장하게 된다.

 

@Entity
@Getter
public class User implements UserDetails {
	
    @Id @GeneratedValue
    private Long id;
    
    private String username;
    private String password;

    //사용자에게 부여된 권한을 지정한 컬렉션으로 반환
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    //계정이 만료되지 않았는가?
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //계정이 잠금상태가 아닌가?
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

	//비밀번호가 만료되지 않았는가?
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
	
    //계정이 활성화 되었는가?
    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

UserDetailsService

  • 사용자가 로그인을 할 때 기본 회원인지 인증하고, 인증이 완료되면 로그인한 상태의 사용자의 세션을 유지시켜주기 위한 인터페이스이다.
  • 기본적으로 해당 인터페이스를 상속받으면 loadUserByUsername 메서드를 구현해야 한다.
  • loadUserByUsername 메서드는 현재 시큐리티를 통해서 로그인한 회원이 기본 회원인지 아닌지 인증하는 메서드이다. 파라미터로 넘어온 username은 html form에서는 id 값에 해당하는 정보이고, JSON 데이터에서는 username에 해당하는 value 값이 넘어온다.
  • 인증에 성공하면 해당 회원에 대한 세션정보가 생성되고 Authentication 객체나 @AuthenticationPrincipal 애노테이션을 통해서 세션에 들어있는 사용자 정보를 꺼내와 사용할 수 있다. 즉, 이 메서드가 정상적으로 완료되어 UserDetails 정보가 반환되면 인증에 성공한 것이다.
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("해당 아이디는 존재하지 않습니다."));
    }
}

 

이렇게 구현한 PrincipalDetailsService는 다음과 같이 시큐리티 설정정보 클래스에 등록해야 정상적으로 사용할 수 있다. 빈으로 등록되어있는 UserDetailsService를 주입받고, 주입받은 객체를 rememberMe().userDetailsService(userDetailsService) 로 권한 요청을 넣어주면 된다. rememberMe().userDetailsService(userDetailsService)는 userDetailsService에 의해서 인증된 사용자 정보를 세션에서 기억한다는 뜻이다.

 

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final UserDetailsService userDetailsService;

    private static final String[] PUBLIC_ACCESS_URL = {"/", "/login", "/register"};

    @Bean
    public PasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers(PUBLIC_ACCESS_URL)
                    .permitAll()
                .and()
                    .formLogin()
                    .loginPage("/login")
                    .loginProcessingUrl("/login")   
                    .defaultSuccessUrl("/", true)   
                .and()
                    .logout()
                    .logoutSuccessUrl("/")
                .and()
                    .rememberMe()
                    .userDetailsService(userDetailsService)
                .and()
                    .oauth2Login()
                    .loginPage("/login")

        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(){
        return (web) -> web.ignoring().antMatchers("/images/**", "/js/**");
    }
}

 

반응형