Sprgin Security Username대신 Email 인증 - f-lab-edu/jshop GitHub Wiki

관련링크1 관련링크2 관련링크3

개요

스프링 시큐리티의 UsernamePasswordAuthenticationFilter 를 사용해, 이메일과 패스워드로 인증하는 작업이다.

상세

시큐리티 필터중 UsernamePasswordAuthenticationFilter 는 시큐리티 필터체인 중간쯤에 위치한 필터이며 역할은 사용자의 이름 username 와 비밀번호 password 로 인증을 하는 필터다.

필터는 내부 메서드 attemptAuthentication 에서 유저 이름과, 비밀번호로 인증토큰을 생성하고 인증토큰의 검증을 AuthenticationManager 에게 위임한다. 성공하면 내부 메서드인 successfulAuthentication 이 수행되고, 실패되면 unsuccessfulAuthentication 이 수행된다.

이 과정에서 유저의 이메일과, 비밀번호를 인증토큰으로 전달하도록 구현했다.

AuthenticationManager 의 구현체는 ProviderManager 이며 이번 프로젝트에서는 건들지 않았다.

ProviderManager 에게 인증 토큰이 들어오면 내부적으로 DaoAuthenticationProvider 에게 전달된다.

DaoAuthenticationProviderUserDetailsService 를 주입받으며 UserDetailsService.loadUserByUsername 메서드로 UserDetails 를 가져온다.

그리고 UserDetails.getUsername, UserDetails.getPassword 를 사용해 인증작업을 수행한다.

내가 구현한 부분은 UserDetailsServiceUserDetails 다.

UserDetailsService.loadUserByUsername 는 인증토큰으로 넘어온 이메일을 사용해 유저를 찾아 UserDetails 타입으로 리턴해주는 역할을 한다.

// CustomUserDetailService.java

   @Override
   public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
       User user = userRepository.findByEmail(email);
        ...

유저 레포지토리에서 이메일을 사용해 유저를 찾고 이를 UserDetails 로 래핑해 리턴한다.

인증 작업에서는 UserDetails.getUsername, UserDetails.getPassword 가 사용되므로, UserDetailsgetUsername 메서드의 리턴값을, email 로 설정했다.

// CustomUserDetails.java

    @Override
    public String getUsername() {
        return user.getEmail();
    }

후기

처음에는 JWT 강의를 따라하며 usernamepassword로 인증하는 방법에 대해 배우게 되었다.

하지만 유저 엔티티의 username 이 아닌 다른 컬럼(email) 로 인증을 하려하니 컴포넌트간 동작원리에 대해 이해가 부족해 진행할 수 없었다.

username 컬럼에 이메일을 저장하는 방법으로 간단하게 해결할 수 있었지만, 보다 근본적인 해결책을 원했다.

시큐리티 공식문서를 보며 필터와 컴포넌트간의 동작 원리에 대해 이해하니 원하는 동작을 수행할 수 있었다.

이 경험으로 다시한번 프레임워크와 라이브러리의 차이에 대해 확실하게 알게되었다.

프레임워크의 경우 개발자는 애플리케이션의 제어권이 없다.

스프링 시큐리티또한 정해진 순서대로 필터가 동작하며 개발자는 이 필터에 자신이 원하는 동작을 정의하는것 뿐, 애플리케이션의 제어를 하지 않는다.

이번 경우에도 인증을 위해 UserDetails 타입의 getUsername, getPassword가 사용되는건 알지만, 개발자가 메서드의 호출을 제어하지는 않는다. 또, 추상화에 대한 큰 장점또한 느꼈다. 위에서 말했듯 개발자는 UserDetails.getUsername 을 직접 호출하지 않는다.

프레임워크 내부에서 getUsername 을 호출하고, 이를 호출하기 위해 인터페이스인 UserDetails 에 의존한다.

또한 UserDetailsService 도 마찬가지로 인터페이스에 대한 의존만 하지 프레임워크로부터 구현체를 주입받아 동작하게 된다.

프레임워크에 대한 이해와 추상화에 대한 이해가 한층 올라가는 경험이였다.