springframework/시작하자SpringSecurity

26.인증 프로세스 구현, CutonUserDetailsService

Jungsoomin :) 2020. 9. 27. 22:30

AuthenticationManager는 UsernamePasswordAuthenticationFilter 에게 호출되어 인증을 AuthenticationProvider 에게 위임한다. 

 

이때 AuthenticationProvider 가 사용하는 객체는 UserDetailsService, UserDetails 객체이다. 

 

각각 유저의 세부정보를 가져오는 역할과 유저정보를 가지고 있는 역할을 한다.

 

DB를 연동하여 이를 사용하려면 UserDetailsServiceUserDetails의 구현은 불가피한 듯 하다.

 

일단 흐름대로 가보면 UserDetailsService > UserDetails > AuthenticationProvider 순.

 


UserDetailsService 의 구현부터 들어간다. 보면, Bean identifier 로 userDetailsService를 주었다.

 

DB 에서 유저정보를 받아내 비교할 것으므로 UserDetailsService loadByUsername 메서드는 DB에서 끌어와야한다.

 

여기서 정보가 없다면 동작 Flow에 맞게 UsernameNotFoundException 을 던진다.

 

정보가 있다면 동작 Flow에 맞게 UserDetails 타입의 객체를 리턴시켜주면 된다. 

 

즉, UserDetails 타입의 객체가 필요하다는 이야기이다.

import com.example.securityapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Account account = userRepository.findByUsername(username);

        if(account == null){
            throw new UsernameNotFoundException("UsernameNotFoundException");
        }


        List<GrantedAuthority> roles = new ArrayList<>();//컬렉션 타입으로 권한정보를 넘겨주어야함
        roles.add(new SimpleGrantedAuthority(account.getRole())); // GrantedAuthority 는 인터페이스, GrantedAuthority 의 구현객체 중 하나인 SimpleGrantedAuthority 사용.

        //UserDetailsService 작동 과정에서 UserDetails 타입으로 리턴해주어야한다.
        AccountContext accountContext = new AccountContext(account, roles);

        return accountContext;
    }
}

User 클래스SpringSecurity 에 정의된 클래스로서 UserDetails 구현하고 있다.

  • 부모 생성자를 호출하여 Entity 객체를 집어넣는다. username, password, Collection<GrantedAuthority> 의 생성자지만 상황에 맞게 Entity 인 Account 객체를 넣었다. 
  • Account 타입의 멤버변수를 선언하여, 나중에 사용하기 위해 Getter 를 정의했다.

** 가만히 보면 생성자의 3번째 파라미터는 Collection<GrantedAuthority> 이다. 이에 맞게 위의 UserDetailsService 구현객체의 loadByUsername 메서드 재정의 블록에서 List<GrantedAuthortity> roles 를 정의 후 구현 객체인 SimpleGrantedAuthority 클래스에 Account의 권한정보를 담아 생성한 후 roles 에 저장했다.

import com.example.securityapp.domain.Account;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

public class AccountContext extends User {// UserDetail 를 구현한 User 클래스를 상속

    private final Account account;

    //생성자 재정의
    public AccountContext(Account account, Collection<? extends GrantedAuthority> authorities) {
        super(account.getUsername(),
                account.getPassword(), authorities);
        this.account = account; // 나중에 account 를 참조하기 위해 멤버변수로 선언
    }

    public Account getAccount() {
        return account;
    }
}

엄연히 "인증" 과정이므로 전에 보았던 회원 가입과는 다르다.

 

UserDetailsService : DB 에서 유저정보를 찾아 UserDetails 타입으로 넘겨야함

UserDetails : 인증과정에서 UserDetailsService 에서 리턴 됨, DB 에서 가져온 VO객체를 생성자로 받아 username , password에 저장해야 함.

 

의 경로 순서로 볼 수 있다.


등록은 Security 설정클래스 : configure(AuthenticationManagerBuilder builder)

 

Authentication Flow 를 고려할 때에 AuthenticationManger가 AuthenticationProvider 에게 인증을 위임한다고 했다.

 

즉 인증과정 에서 사용될 UserDetailsService 는 AuthenticationManager에 등록해야한다.

 

AuthenticationManagerBuilder userDetailsService(userDetailsService) 메서드로 등록가능하다.

@Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {// 인증시 구현한 UserDetailsService 객체를 등록하려면 해당 메서드 재정의
        auth.userDetailsService(userDetailsService);//등록
    }