UserDetailsService, UserDetailsManager
loadUserByUsername
인터페이스 내부에 loadUserByUsername이라는 추상 메소드가 존재합니다. 보편적인 인증 작업을 생각해보면 가장 먼저 데이터베이스에 저장되어 있는 유저 세부 정보 및 브라우저에 입력된 유저의 세부 정보를 불러오는 것입니다. 이를 위해 loadUserByusername이라는 추상 메서드가 존재합니다. 여기서 우리는 의문을 가질 수 있습니다.
- 왜 데이터베이스에서 유저 이름만 불러오는가?
- 유저 이름과 비밀번호를 둘 다 비교하는 것이 좋지 않은가?
이유는 바로, 비밀번호를 불필요하게 네트워크로 전송하면 안되기 때문입니다. 보안 문제에 취약하므로, 먼저 유저 이름을 사용하여 유저 세부 정보를 로드하고, 나중에 비밀번호를 비교하는 논리를 추가하면서 다른 인증 과정을 거쳐야 합니다.
UserDetailsManager라는 인터페이스가 존재하는데, 유저 세부 정보를 관리하는 데 도움을 줍니다. 세부 정보를 불러오는 것 외에도, 유저 생성, 삭제, 업데이트 등 다양한 작업을 할 수 있게 도와줍니다. InMemoryUserDetailsManager에서는 위에서 언급한 유저 세부 정보를 관리하는 데 도움을 주는 메서드들을 구현해놓았으며 이외에도 JdbcUserDetailsManager, LdapUserDetailsManager등이 존재합니다. InMemoryUserDetailsManager를 포함한 세 가지 클래스는 Spring Security 프레임워크에서 중요한 역할을 합니다.
UserDetails와 User
UserDetails 인터페이스와 그 구현 클래스를 사용하면 유저의 세부 정보를 나타낼 수 있습니다. 따라서 유저의 이름, 비밀 번호, 권한과 같은 세부 정보는 UserDetails 구현 클래스의 객체 내에 저장될 수 있으며, 이 인터페이스와 그 구현 클래스는 InMemoryUserDetailsManager, JdbcUserDetailsManager 등과 같은 모든 상위 클래스와 인터페이스 내에서 사용됩니다.
getAuthorities, getPassword 등과 같은 메소드들은 모두 유저의 계정 세부 정보를 이해하는 데 도움을 주는 메소드들입니다. User 클래스는 UserDetails에서 정의된 메소드들을 구현하여 사용하고 있습니다. 우리는 User 클래스에 작성된 기본 구현을 사용하거나 자체 UserDetails 구현 클래스를 작성할 수 있습니다. 우리에게 맞는 방법을 선택하면 됩니다. 여기서 흥미로운 점은 메소드 목록에서 setter 메소드를 찾아볼 수 없습니다. 그 말은 즉, 이 객체 내에 속성들을 읽기만 할 수 있습니다. 생성자를 사용하여 객체가 만들어진 후에는 성공적인 인증 이후에 유저 이름, 비밀번호, 권한과 같은 값들을 누군가가 재정의하는 것을 막기 위해서입니다.
여기서 의문을 가지게 될 수도 있습니다. Spring Security Filter를 거친 사용자 정보는 2단계에서 UsernamePasswordAuthentication Token에 저장된다고 했는데, AuthenticationProviders 내에서 왜 다시 저장하는걸까?
User 클래스와 UserDetails 인터페이스 내에는 getPassword, getUsername, isAccountNotExpired과 같은 메소드들이 존재합니다. 그래서 UserDetails와 User는 유저의 세부 정보를 DB 메모리 등 저장 시스템에서 로드하려고 할 때 사용됩니다. 예를 들면, JdbcUserDetailsManager, InMemoryUserDetailsManager 등에서 사용자 정보를 불러옵니다. 유저 세부 정보가 데이터베이스에서 로드되면 이러한 세부 정보는 AuthenticationProviders에 다시 전달됩니다. Providers 내에서 인증이 성공하면 모든 정보와 함께 성공적인 인증 세부 정보를 인증 객체 데이터 유형으로 바꿉니다. 여기서 인증 객체란 우리가 기존에 알던 UsernamePasswordAuthenticationToken을 말합니다.
Principal, Authentication
UsernamePasswordAuthenticationToken
Authentication 인터페이스는 Principal을 상속받고, UsernamePasswordAuthenticationToken 은 Authentication의 구현체입니다. 내부 메소드를 살펴보면 getPrincipal, getAuthorities, isAuthenticatied, getDetails 와 같은 메소드들이 존재합니다. 뭔가 느낌이 다르다는 게 느껴지시나요? UserDetails에서는 사용자 정보를 DB에서 불러올 때 사용합니다. 사용자 정보, 비밀번호, 계정 만료 여부, 인가 여부 등을 확인할 수 있었습니다. 그렇다면 Authentication에서는 어떤 역할을 할 것 같나요? 바로 인증이 되었는지, 되지 않았는지에 대한 내용을 다루게 됩니다. 사용자 정보를 갖고 있다는 점에서 비슷하지만 하는 역할이 다름을 알 수 있습니다.
DaoAuthenticationProvider의 Authenticate()
여기서는 유저를 인증하려고 합니다. 인증이 성공하면, createSuccessAuthentication 메서드를 호출합니다.
이 메소드는 UserDetails에서 받아온 정보를 Authentication 타입으로 필요한 정보를 채우고 반환하는 모습을 볼 수 있습니다. 이로서 UserDetails와 Authentication 의 다른 역할을 이해할 수 있게 되었습니다.
출처
- Spring Security Docs
- https://www.udemy.com/course/spring-security-6-jwt-oauth2-korean/