이번 강의에서는 UserDetails 인터페이스와
그 구현에 대해 자세히 이야기해 보겠습니다
이전 강의에서 말씀 드린 것처럼
UserDetails 인터페이스와
그 구현 클래스를 사용하면 엔드 유저의 세부 정보를 나타낼 수 있습니다
따라서 엔드 유저의 이름, 비밀번호, 권한과 같은 세부 정보는
UserDetails 구현 클래스의 객체 내에
저장될 수 있습니다
따라서 이 인터페이스와
그 구현 클래스는 InMemoryUserDetailsManager
JdbcUserDetailsManager 또는
UserDetailsService와 같은 모든 상위 클래스와
인터페이스 내에서 사용됩니다
따라서 UserDetails 화살표 위에 있는
이 인터페이스 또는 클래스 중 어떤 것을 선택하든
이러한 클래스와 인터페이스는
UserDetails를 메소드 반환 유형 또는
해당 클래스가 보유한 비즈니스 로직 내에서 사용합니다
그래서 UserDetails 인터페이스와
그 구현 클래스 내에 정확히 무엇이 있는지 이해하는 것이
매우 중요합니다
그러면 이제 그 세부사항을 이해해 보겠습니다
여기에서 볼 수 있듯이
이것이 UserDetails 인터페이스입니다
인터페이스이기 때문에
모든 추상 메소드를 포함하고 있을 것입니다
이 인터페이스 내에 있는 메소드 중
첫 번째는 getAuthorities입니다
이 메소드는 엔드 유저의 권한 또는 역할 목록을
보유하고 있고 그래서 이를 사용하여
우리는 향후 강의에서 논의할 인가 또는
역할 기반 액세스 메커니즘을 구현할 수 있습니다
그리고 다음으로 getPassword, getUsername가 있습니다
이 메소드들은 엔드 유저의 비밀번호와 유저 이름을 반환합니다
그리고 UserDetails의 이러한 기본 세부 정보 이후에
유저 계정이 만료되었는지, 유저 계정이 잠겨 있는지
유저 자격 증명이 만료되었는지
유저 계정이 활성화되었는지 또는 비활성화되었는지를
확인하는 데 도움이 되는 몇 가지
다른 메소드를 지원합니다
이 모든 시나리오에 대해
엔드 유저의 계정 세부 정보를 이해하는 데
도움이 되는 메소드들도 있습니다
여기를 보시면 이 모두 추상 메소드들입니다
여기에 있는 메소드 주석을 읽어보시면 됩니다
보시면 첫 번째 메소드는 유저에게
부여된 권한을 반환하며
두 번째는 유저를 인증하는 데
사용된 비밀번호를 반환합니다
그리고 세 번째는 엔드 유저의 유저 이름을 반환하는
getUsername입니다
이제 나머지 메소드로 넘어가보면
isAccountNonExpired가 있습니다
이 값이 true인 경우
유저 계정이 유효하며 만료되지 않았음을 나타내고
false인 경우 계정이 더 이상 유효하지 않으며
계정이 만료되었음을 나타냅니다
그래서 isAccountNonLocked, isCredentialsNonExpired
isEnabled에 대해서도 동일한 원리가 적용됩니다
Spring Security 팀은 단순히 인터페이스를 정의하는 데
그치지 않았습니다
그들은 또한 이 인터페이스들의 샘플 구현도 제공합니다
이를 우리 프로젝트와 일상적인 개발에서
사용할 수 있습니다
여기에서 볼 수 있듯이 User 클래스를 참조하여
구현에 대한 참고 자료를 확인하라고 합니다
원하시면 우리만의 구현을 작성할 수도 있습니다
아무도 그것을 막지 않을 것입니다
하지만 User 내에서 제공된 기본 구현이 만족스러우면
UserDetails의 우리만의 구현을
작성하는 것은 의미가 없습니다
따라서 Use'를 사용할지 아니면
자체 UserDetails 구현 클래스를
작성할지는 여러분의 선택입니다
이 User 클래스 내에서는
UserDetails 인터페이스 내에 보았던
모든 메소드들이 동일하게
오버라이드되어 있을 것입니다
보시다시피 getPassword 메소드가 있으며
이 메소드는 간단히 비밀번호 값을 반환합니다
하지만 여기서 비밀번호 값은
어떻게 채워지는 궁금증이 생길 수 있습니다
그래서 이를 위해 User 클래스의 생성자를 확인하면
두 개의 생성자를 사용하여
이 클래스의 객체를 만들 수 있습니다
하나는 어떤 불리안 (Boolean) 값을
전달하지 않는 생성자로 유저 이름, 비밀번호, 권한을 전달하고
나머지 모든 불리안 (Boolean) 값은
각각 true, true, true, true로 설정되어 있습니다
이는 계정이 만료되지 않았고 잠기지 않았으며
비활성화되지 않았음을 나타냅니다
반면 이러한 불리안 (Boolean) 값을 전달하려면
또 다른 생성자도 있습니다
보시다시피 여기 또 다른 생성자입니다
그래서 이 User 객체를 생성하는 유저는
이러한 세부 정보를 전달해야 하며 누군가가
getPassword, getUsername을 액세스하려고 할 때
해당 메소드 호출에 따라
이러한 세부 정보가 제공될 것입니다
여기서 강조하고 싶은
또 다른 흥미로운 점은
UserDetails와 User 클래스 내에 있는
메소드 목록을 확인해 보면 setter 메소드가 없습니다
오직 속성을 읽을 수 있는 getter 메소드만 있습니다
그 말은 즉 이 객체 내에 있는 속성들을
읽기만 할 수 있습니다
생성자를 사용하여 객체를 만든 후에는
setter 메소드를 사용하여
값을 재지정하는 것이 불가능합니다
왜냐하면 이는 보안과 관련이 있기 때문에
생성자를 사용하여 객체가 만들어진 후에는
성공적인 인증 이후에
유저 이름, 비밀번호, 권한과 같은 값들을
누군가가 재지정하는 것을 허용하고 싶지 않기 때문입니다
그래서 누군가가 이러한 값을 덮어쓸
가능성이 없도록 하기 위해서입니다
그래서 이는 Spring Security 팀에 의해 설계된
또 다른 좋은 디자인 패턴입니다
여기 InMemoryUserDetailsManager에 있는
메소드를 확인한다면
AuthenticationProvider에 의해 호출되는
LoadUserByUsername이라는 메소드가 있습니다
여기서는 유저 이름을 기반으로 맵에서
세부 정보를 로드하려고 하는 것을 볼 수 있습니다
이 맵은 애플리케이션 시작 중에
UserDetails를 구성하고
웹 애플리케이션의 메모리에
저장한 것일 수 있습니다
따라서 이 맵 내에 주어진 이름을 가진 유저가 있다면
해당 세부 정보가 채워질 것입니다
그리고 동일한 세부 정보를 사용하여
생성자를 호출하여 유저 객체를 만듭니다
유저 객체가 생성되면
동일한 객체가 반환됩니다
형변환 문제에 대해 걱정할 필요가 없습니다
왜냐하면 이 UserDetails와 User는
부모 자식 관계를 가지고 있기 때문입니다
그러므로 여기에서는 어떤 문제도 발생하지 않을겁니다
보시면 여기서 의문이 생길 수 있습니다
내부 플로우를 살펴보면
저는 모든 인증 세부 정보가
성공적인 인증이 발생했는지 여부와 같은
유저 이름 같은 UserDetails 관련된
모든 세부 정보가 인증 객체 내에
저장될 것이라고 말했습니다
또한 두 번째 단계에서
모든 것이 인증 객체에 채워지며
성공적인 인증 이후에는 동일한 것이
보안 컨텍스트에 저장됩니다
그래서 여기서
왜 인증 객체가 있고 UserDetails 인터페이스와
User 클래스가 있는지 또 이들 간에 어떤 관계가 있는지
의문이 생길 수 있습니다
이에 대한 답은 이 슬라이드를 확인하시면 됩니다
이 슬라이드를 보시면
이 분도 왜 로그인 유저 세부 정보를
저장하는 두 가지 별개의 방법이 있는지에 대한
비슷한 의문을 가지고 있습니다
첫 번째는 UserDetails와 User 클래스 입니다
이 클래스와 인터페이스 내에는 getPassword
getUsername, getAuthorities 등의 메소드가 있습니다
그래서 UserDetails와 User는
유저의 세부 정보를
데이터베이스 메모리 등 저장 시스템에서
로드하려고 할 때 사용됩니다
특히 UserDetailsService 및 UserDetailsManager의
인터페이스 및 구현 클래스 내에서 사용됩니다
유저 세부 정보가 데이터베이스에서
로드되면 이러한 세부 정보는 확실히
AuthenticationProviders에 다시 전달됩니다
따라서 AuthenticationProviders 내에서
인증이 성공하면
이 AuthenticationProviders는
모든 정보와 함께 성공적인 인증 세부 정보를
인증 객체 데이터 유형으로
변환하는 것이 책임입니다
그래서 왼쪽 하단에
참고 사항이 표시되어 있습니다
따라서 어떤 시나리오에서든
인증이 성공했는지 여부를 판단하려고 할 때
반환 유형은 인증입니다
그렇다면 인증이 성공했는지
여부를 어디서 확인할까요?
바로 AuthenticationProviders 내에서 확인합니다
따라서 AuthenticationProviders 외에도
ProviderManager및 필터 내에서
항상 인증 객체를 처리합니다
왜냐하면 인증 객체를 사용하여
유저 이름, 비밀번호, 권한과 같은
인증과 관련된 세부 정보만
저장할 수 있습니다
하지만 계정이 만료되었는지
계정이 비활성화되었는지
또는 활성화되었는지와 같은 세부 정보를
저장할 필요는 없습니다 왜냐하면
이러한 조건이 충족될 때만 인증이 성공합니다
그러므로
Spring Security 프레임워크의 내부 구성 요소로
다시 유저 세부 정보를 전달할 필요가 없습니다
그래서 여기서 AuthenticationProvider가
UserDetailsService
및 UserDetailsManager에서 받은
유저 세부 정보를 인증 객체 유형으로
변환하는 마법을 보여줄 겁니다
물론 우리가 가진
인증의 구현 중 하나는
UsernamePasswordAuthenticationToken입니다
그래서 이 인증 인터페이스는
Java 라이브러리의
Principal 인터페이스를 확장합니다
Principal 인터페이스는
엔터티의 이름이나 개인의 이름 또는 로그인 ID를 나타냅니다
그래서 Principal에서
Authentication 인터페이스는 getPrincipal이라는
메소드를 상속하며 그리고 Authentication 내에는
getName, getAuthorities와 같은 다른 메소드가 있습니다
이를 코드 내에서 빠르게 확인할 수 있습니다
하지만 그 전에 UserDetails와
Authentication에 대해 매우 명확한지
또 왜 우리는 UserDetails를 두 가지 별개의
방식으로 나타내는지에 대해 확인하고 싶었습니다
이 슬라이드가 여러분에게 대답을 제공하고
언제든지 동일한 내용을 참조하실 수 있습니다
이제 Authentication 인터페이스를 탐험해 보겠습니다
여기 Authentication 인터페이스를 열면
Principal을 확장 합니다
이 Principal은 Java 라이브러리에서 제공됩니다
그리고 getName이라는 단 하나의 메소드만을 가지고 있습니다
이전에는 getPrincipal이라고 말했지만 그것은 잘못된 말이었습니다
그러므로 Principal 내에 있는 유일한 메소드는 getName입니다
이제 Authentication을 확인하면
getPrincipal, getDetails, getAuthorities
getCredentials, isAuthenticated
setAuthenticated와 같은 메소드가 있습니다
isAuthenticated는 프레임워크가 주어진 유저가
성공적으로 인증되었는지 여부를 이해하는 데 사용됩니다
그리고 setAuthenticated는 프레임워크가
isAuthenticated의 값을 설정하는 데 사용됩니다
성공적인 인증이 완료되면
이 메소드를 사용하여 true 값을 설정하고
그렇지 않으면 false 값을 설정합니다
그래서 InMemoryUserDetailsManager를 확인하면
여기에서는 저장 시스템에서
유저의 세부 정보를 불러옵니다
그래서 반환 유형이 UserDetails입니다
반면에 DaoAuthenticationProvider를 확인하면
여기서는 유저를 인증하려고 합니다
스크롤을 내리면 유저 세부 정보가
이 Authenticate 메소드 내에 채워진 후에
인증이 성공하면
createSuccessAuthentication이라는
메소드를 호출하는 것을 볼 수 있습니다
그래서 이 메소드는
UserDetails에서 Authentication으로 필요한
모든 세부 정보를 채우고
동일한 것을 반환하는 것입니다
이 메소드가 호출되면
여기서 반환 문이 있고
메소드 시그니처에서도 반환 유형이
authentication으로 지정되어 있습니다
이로써 우리의 논의가 확인되었습니다
UserDetails와 Authentication 간의 관계에 대해
매우 명확하게 이해하셨기를 바랍니다
왜냐하면 이건 매우 중요한 주제이고
이해하지 못하면 앞으로
진행될 강의에서 우리가 다룰 모든 주제에 대해
쉽게 혼란스러워질 수 있습니다
그럼 여러분들에게 매우 명확하게 전달되었기를 바랍니다
감사드리며 다음 강의에서 뵙겠습니다