이전 강의에서는
Spring Security 내부 흐름을 다뤘습니다
이 강의에서는
Spring Security 프레임워크 코드를 디버그함으로써
이 Spring Security 내부 흐름을 확인해보겠습니다
그러기 위해 IntelliJ IDEA로 접속 하겠습니다
Spring Security 내부 흐름에서
중요한 역할을 하는 첫 번째 구성 요소는
Spring Security 필터입니다
인증 인가를 수행하고
로그인 페이지를 표시하고
HDT 포지션 내부에
인증 정보를 저장하는 것을
도와주는 Spring Security
내부 필터가 여럿 있습니다
모든 필터는 각자의 역할과 책임이 있는데
여기서는 우리가 마찬가지로 디버그할 수 있도록
몇 가지 중요한 Spring Security 내부 필터만 강조하겠습니다
첫 번째는 인가 필터입니다
이 필터의 책임은
엔드 유저가 접근하고자 하는
URL에 접근을 제한하는 것입니다
공개 URL이라면 응답은 엔드 유저에게
자격 증명을 요구하지 않고
바로 표시될 것입니다
추후 강의에서는
Spring Security 프레임워크의 도움을 받아
공개 URL을 설정하는 방법을 다룰 예정입니다
하지만 보안 URL에 접근하고자 한다면
이 필터는 해당 URL에 접근을 멈추고
해당 요청을 Spring Security 필터 체인의
다음 필터로 리디렉트할 것입니다
스크롤을 내려보시면
doFilter라는 메소드가 있습니다
이 메소드 내에서는
인가 관리자의 도움을 받아
특정 URL이 공개 URL인지
보안 URL인지 체크한 후
그에 따라 URL에 접근을 허용하거나
URL 접근을 거부합니다
보안 URL에 접근하려고 한다면
그 다음 등장할 필터는
DefaultLoginPageGenerating 필터입니다
우리가 보안 URL에 접근하려고 하면
로그인 페이지가 표시되는 것을
전에 확인했는데
그 페이지는 이 필터의 도움으로 생성됐습니다
여기 generateLoginPageHtml 안을 보면
해당 로그인 페이지와 관련된 모든 HTML 코드가
프레임워크에 의해 생성됐다는 것을 볼 수 있으며
이는 엔드 유저에게 표시됩니다
엔드 유저가 본인의 유저 네임, 비밀번호와 같은
자격 증명을 입력하고 나면 다음 등장할 필터는
UsernamePasswordAuthentication 필터입니다
이 필터 안에는
attemptAuthentication이라는 메소드가 있습니다
이 필터의 주된 책임은
수신하는 http의 출력 요청으로부터 유저 네임과
비밀번호를 추출하는 것입니다
이 요청으로부터 필터는 유저 네임과
비밀번호를 추출하고 이 유저 네임과 비밀번호의 도움으로
UsernamePasswordAuthenticationToken
객체를 생성합니다
여기서 여러분에게 의문이 들 수 있습니다
Spring Security 내부 흐름도에서 우리는
2단계에서 프레임워크가 인증 객체를
생성할 것이라고 했습니다
근데 여기서는 UsernamePasswordAuthenticationToken라는
다른 객체가 보입니다
이것을 더 이해해보겠습니다
우리가 이 클래스를 열면
이 클래스는 내부적으로 AbstractAuthenticationToken을 확장하고
이 AbstractAuthenticationToken을 열면
이 클래스는 인증 인터페이스를 적용합니다
인증은 인터페이스이고
우리가 이 인증 인터페이스의 객체를 생성할 수는 없기에
제 필터는 UsernamePasswordAuthenticationToken
객체를 생성하게 됩니다
이 객체는 Authenticate 메소드를 호출함으로써
인증 관리자에게
넘기게 됩니다
여기서도 인증 관리자는 인터페이스입니다
Spring Security 프레임워크 안에는 ProviderManager라는
클래스가 있습니다
이건 인증 관리자의 구현입니다
보시다시피
이 클래스는 인증 관리자를 구현합니다
그리고 이 클래스 안에는 Authenticate이라는 메소드가 있습니다
인증 관리자의 역할은
팀의 매니저와 굉장히 유사합니다
팀의 매니저는 주로
개발자, 테스터, 비즈니스 애널리스트 등으로 구성된
팀을 리드합니다
모든 팀원들과 조율함으로써
매니저는 성공적으로
클라이언트 요구 사항을 구현해야 합니다
여기서도 유사하게 ProviderManager는
인증 관리자의 구현이며
여기서의 역할은 프레임워크 내에
사용 가능한 모든 인증 제공자
또는 개발자가 정의한 인증 제공자와
상호 작용을 시도할 것입니다
여기로 스크롤을 내려보시면 반복문이 있습니다
이 반복문은 모든 적용 가능한 인증 제공자를 반복할 것이고
ProviderManager는
인증 성공이나 인증 실패를
확인할 때까지
이 인증 제공자들을 반복할 겁니다
여러분의 로그인 흐름에 2개의 적용 가능한
인증 제공자가 있다고 하면
이런 시나리오에서 첫 번째 인증 제공자가
결과를 인증 성공으로 판단한다면 프레임워크는
두 번째 인증 제공자를 시도하지 않을 겁니다
반면 첫 번째 인증 제공자가
인증 실패를 반환한다면
이 ProviderManager의 프레임워크는 또한
다음 인증 제공자를 시도하게 됩니다
그러니 모든 인증과 인가의 로직은
이 인증 제공자들 내부에 있습니다
추후 강의에서는 우리의 자체적인 인증 제공자를
생성하는 방법을 다루겠습니다
일단은 우리가 다룰 기본 흐름에 있어서
ProviderManager는 Spring Security의
인증 제공자 중
하나를 호출하는데
바로 DaoAuthenticationProvider입니다
DaoAuthenticationProvider 내부에는
Authenticate이라는 메소드가 있습니다
이 Authenticate 메소드에
ProviderManager는 요청을 전송하고
이 메소드 안에서 여러분은
모든 실제 인증 로직이 반환되는 것을 확인할 수 있습니다
제일 먼저
유저 네임이 무엇인지 불러오려고 합니다
이 유저 네임을 활용해
retrieveUser 메소드를 호출할겁니다
우리는 지금 AbstractUserDetailsAuthenticationProvder라는
클래스 안에 있습니다
이건 DaoAuthenticationProvider에 의해 확장되며
이 DaoAuthenticationProvider 안에서
retrieveUser를 찾아보면
여기서는 이 인증 제공자가
UserDetailsManager 또는 UserDetailsService의
도움을 받는 것을 볼 수 있습니다
내부 구조도에서 이야기했듯이
모든 인증 제공자는 UserDetailsManager나
UserDetailsService와 함께 Password Encoderd의
도움을 받습니다
Spring Security 팀이 이렇게 많은
구성 요소를 개발한 이유는
모든 구성 요소가 고유의 역할과
책임이 있기 때문입니다
모든 로직을 하나의 구성 요소에 욱여넣기보다
쉽게 관리할 수 있도록
모든 역할과 책임을 분리해두었습니다
우리는 Spring Security 필터의 역할이 뭔지 알고
인증 관리자의 역할도 알고
인증 제공자들의 도움으로
실질적인 인증 로직(authentication logic)을 정의할 겁니다
우리가 어떤 인증을 수행할 때마다
우선적인 요구 사항은 당연히
개발자가 가질 일반적인 요구 사항은
저장소에서 유저 정보를 불러오는 것입니다
저장소에서 유저 정보를 불러오려면
UserDetailsManager 또는 UserDetailsService라는 또 다른
구성 요소가 있습니다
유사하게 Password Encoder도 있는데
우리가 비밀번호를
저장소에 저장하거나 저장소에서
비밀번호를 읽어오려고 할 때마다
일반적인 텍스트 형식으로
비밀번호를 처리하는 대신
Password Encoder의 도움으로
암호화하거나 해싱할 수 있습니다
여기서 저의 DaoAuthenticationProvider는
UserDetailsManager의 구현체 중 하나로부터 도움을 받을 겁니다
이 클래스는 InMemoryUserDetailsManager입니다
보시다시피
이것은 UserDetailsManager라는 인터페이스를
구현하는 클래스입니다
프레임워크는 우리가 유저 정보를 애플리케이션의
인메모리 내부에 저장하려고 할 때마다
이 InMemoryUserDetailsManager를 채택할겁니다
기억하신다면 여기서 우리는
유저 자격 증명을 application.properties에
저장하고 있습니다
우리가 이 속성을 통해 유저 네임과
비밀번호를 저장할 때마다 유저 정보는
애플리케이션의 인메모리 내부에 저장됩니다
그래서 프레임워크가 이 InMemoryUserDetailsManager를
활용하려는 것입니다
이 클래스에서는 loadUserByUsername이라는
메소드를 호출합니다
여기를 보시면 유저 네임 기반으로
유저 정보를 불러오려고 하고 있습니다
이 유저들은 우리가 application.properties 내부에
설정한 유저 정보에 기반해
실행 시점에 채워져 있을 겁니다
유저 정보가 채워진 다음
retrieveUser 메소드가 Authenticate 메소드에
유저 정보를 반환하는 것을 볼 수 있습니다
이 Authenticate 메소드 안에서
우리는 동일한 유저 정보를
additionalAuthenticationChecks와 같은
추가 메소드에 전달하고 있습니다
이 메소드 구현은 DaoAuthenticationProvider
내부에 있습니다
additionalAuthenticationChecks라는
이 메소드를 찾아보면
저장소로부터 불러온 유저 정보에 기반해
Password Encoder를 활용하여
UI에서 제공한 비밀번호와
저장소 내부의 비밀번호를
맞추고 있음을 알 수 있습니다
여기서도 Password Encoder가 역할을 하고 있습니다
우리는 비밀번호를 일반 텍스트로 저장했기 때문에
프레임워크가 기본 Password Encoder를
활용하게 됩니다
Password Encoder만을 다루는 섹션이 있습니다
어떤 다양한 Password Encoder가 있는지
비밀번호를 처리할 때 준수할
가장 이상적인 관행은 무엇인지
이런 것들은 Password Encoder 관련 주제만을
다루는 섹션에서 다시 이야기하겠습니다
Password Encoder가 충족하고
this.matches 메소드가 비밀번호가 일치한다는 의미로
참이라는 불리안을 반환한다면
이 응답은 ProviderManager에 전달되고
같은 내용은 프레임워크에 전달됩니다
인증이 성공적이라면
프레임워크는 보호된 API URL 우리 보안 웹페이지를
표시하게 됩니다
이것이 중요한 지식 전부입니다
우리의 REST API를 호출함으로써
이 흐름 전체를 디버그 해보겠습니다
브라우저입니다
localhost:8080/welcome에 접근하려고 합니다
접근을 시도하자마자
가장 먼저 등장하는 필터는
인가 필터인 걸 볼 수 있습니다
보안 APA 경로에 접근하려고 하기 때문에
다음 등장하는 필터는
DefaultLoginPageGenerating 필터입니다
인가 필터는 여러 군데서
등장할 것이기 때문에
이 중단점을 삭제하겠습니다
이 중단점에서 계속해서 멈추고 싶지 않기 때문에
이 중단점을 해제하겠습니다
우리는 지금 generateLoginPage 메소드 안의
DefaultLoginPageGenerating 필터 안에 있습니다
이 중단점도
계속해서 멈추지 않도록
이 중단점을 해제하겠습니다
이제 엔드 유저에게 로그인 페이지가 표시됩니다
제 자격 증명을 입력하겠습니다
eazybytes 비밀번호는 12345
로그인 버튼을 클릭하겠습니다
로그인 버튼을 클릭하자마자
UsernamePasswordAuthentication 필터가
호출된 것을 볼수 있고 이 필터는 저의 유저 네임과
비밀번호를 추출합니다
이것이 저의 유저 네임 eazybytes이고
이것이 제가 입력한 비밀번호입니다
이 정보를 가지고 이 필터는
UsernamePasswordAuthenticationToken 타입 객체를 만듭니다
이 시점에서
ProviderManager 내부의
Authenticate 메소드를 호출합니다
이 중단점을 해제하겠습니다
여기서 보시다시피
ProviderManager 내부에서 이 안에
Authenticate 메소드가 있습니다
이 Authenticate 메소드 안에서
Spring Security 프레임워크 내부의
모든 인증 제공자는 이 ProviderManager에 의해
실행됩니다
이 중단점을 해제하자마자
다음 탭은 DaoAuthenticationProvider
클래스 내부에 있습니다
왜 중단점이 AbstractUserDetailsAuthentication 안에서
멈췄는지 헷갈리지 마십시오
왜냐하면 제 daoAuthenticationProvider는
AbstractUserDetailsAuthenticationProvider를 확장합니다
그래서 두 클래스가 동일한 이유입니다
Authenticate 메소드가
AbstractUserDetailsAuthenticationProvider 안에 있습니다
그래서 중단점이 여기서 멈췄습니다
이 클래스 안에서는 앞서 얘기했듯이
retrieveUser 메소드가 호출됩니다
이 중단점을 해제하겠습니다
보시다시피 중단점을 해제하자마자
인증 제공자는 UserDetailsManager의
구현체 중 하나인 InMemoryUserDetailsManager로부터
도움을 받으려고 합니다
여기서 유저네임에 기반해 저장소로부터
유저 정보를 불러오고
이 유저 정보는
DaoAuthenticationProvider에 전달됩니다
이 중단점을 해제 해보겠습니다
이 유저 정보를 활용해
인증 제공자는
Password Encoder의 도움을 받아 비밀번호가 일치한다면
성공적인 인증 객체를
ProviderManager에 전송하고
ProviderManager은 같은 내용을
Spring Security 필터에 전달합니다
인증이 성공적이었으니
엔드 유저는 성공적인 응답을 얻게 됩니다
이것을 브라우저에서 확인해 보겠습니다
보시다시피 유효한 응답을 받았습니다
굉장히 긴 강의였지만
여러분이 이 내부 흐름과 관련된
모든 구성 요소를 명확히 이해하고
우리가 다룬 모든 클래스와 인터페이스를
매핑하고 있기를 바랍니다
다음 강의에서는
여러분이 더 명확히 이해하도록
우리가 이 강의에서 다룬 내용의
시퀀스 플로우를 다룰 겁니다
감사드리며 다음 강의에서 뵙겠습니다
수고하셨습니다