[Spring Security] UserDetails, UserDetailsService

UserDetails

스프링 시큐리티에서 사용자의 정보를 담는 인터페이스이다. 하지만 대부분의 경우 기본 UserDetails 인터페이스로는 구현에 한계가 있기 때문에 다음과 같이 UserDetails를 구현하여 사용한다. 스프링 시큐리티는 로그인 요청을 가로채서 인가처리를 하는데, 기존 UserDetails 대신 그것을 구현한 객체를 저장,관리하게끔 하자. UserDetails를 구현한 객체(PrincipalDetails)를 만들고, UserDetailsService를 구현한 객체(PrincipalDetailsService)에서 그것을 저장하면 시큐리티가 커스텀한 UserDetails를 사용하여 관리해주는 것 같다.

 

//스프링 시큐리티가 로그인 요청을 가로채서 UserDetails를 구현한 객체를 저장하게끔 하자.
//UserDetails를 구현한 객체(PrincipalDetails)를 만들고, UserDetailsService를 구현한 객체(PrincipalDetailsService)에서 그것을 저장.
class PrincipalDetails(private val user: User):UserDetails { //DI 생략(생성자 자동)

    override fun getPassword(): String {
        return user.password!!
    }

    override fun getUsername(): String {
        return user.email!!
    }

    //계정이 만료되지 않았는지를 리턴
    override fun isAccountNonExpired(): Boolean {
        return true
    }

    //계정이 잠기지 않았는지를 리턴
    override fun isAccountNonLocked(): Boolean {
        return true
    }

    //비밀번호가 만료되지 않았는지를 리턴
    override fun isCredentialsNonExpired(): Boolean {
        return true
    }

    //계정이 활성화되지 않았는지를 리턴
    override fun isEnabled(): Boolean {
        return true
    }

    //계정의 권한 리턴
    override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
        val collectors: MutableCollection<GrantedAuthority> = ArrayList()
        collectors.add(GrantedAuthority {
            user.role.toString()
        })
        return collectors
    }

 

(후술할 UserDetailsService에서 UserDetails를 이용해서 사용자 정보를 가져오게 되는데, 개발자가 만든 DTO에 로그인 정보를 받아올 때 오버라이딩한 여러 함수를 통해서 계정 정보를 같이 가져온다. 여기서는 User라는 DTO에 사용자 정보를 가져오도록 만들었다.)

 

오버라이딩할 함수들은 다음과 같은 기능을 한다.

 

출처: https://programmer93.tistory.com/68

 

 

 

 

UserDetailsService

스프링 시큐리티에서 유저 정보를 가져오는 인터페이스인데, 이 인터페이스를 구현하는 서비스를 만들고,  loadUserByName함수를 오버라이딩해주면 커스텀한 UserDetails를 사용할 수 있다.

@Service
class PrincipalDetailsService:UserDetailsService {

    @Autowired
    private lateinit var userRepository:UserRepository

    //로그인 요청(username, password)를 가로챌때 username이 DB에 있는지 확인하여 리턴 (pw는 알아서 처리)
    override fun loadUserByUsername(email: String?): UserDetails {
        val principal= userRepository.findByEmail(email)
        if(principal==null)
            throw UserNotFoundException("해당 사용자를 찾을 수 없습니다")

        return PrincipalDetails(principal) //시큐리티 세션에 커스텀한 UserDetails 저장
    }
}

 

이 서비스에서는 login요청이 오면 자동으로 UserDetailsService타입으로 의존성 주입되어있는 loadUserByUsername함수가 자동으로 실행된다. 이 함수는 스프링 시큐리티에서 넘겨준 loginId를 바탕으로 DB를 조회 후에 UserDetails타입으로 정보를 반환해준다.

 

 

// 단, 이 때 loadUserByUsername의 인자로 들어가는 username은 로그인시 id를 의미하므로, 뷰에서 form등으로 보낼때 반드시 name="username"과 같은 형태로 보내주어야 한다.