로그인은 어떻게 진행될까?
-
사용자가 로그인을 요청한다.
-
AuthenticationFilter (사용할 구현체 : UsernamePasswordAuthenticationFilter)가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트한다. Front 단에서 유효성 검사를 할 수 있지만 서버에서도 가능하다. HttpServletRequest에서 꺼내온 사용자 아이디와 패스워드를 진짜 인증을 담당할 AuthenticationManager 인터페이스 (구현체: ProviderManager)에게 인증용 객체(UsernamePasswordAuthenticationToken)로 만들어줘서 위임한다.
-
AuthenticationFilter에게 인증용 객체(UsernamePasswordAuthenticationToken)을 전달받는다.
-
실제 인증을 할 AuthenticationProvider에게 Authentication 객체 (UsernamePasswordAuthenticationToken)을 다시 전달한다.
-
DB에서 사용자 인증 정보를 가져올 UserDetailsService 객체에게 사용자 아이디를 넘겨주고 DB에서 인증에 사용할 정보 (id/password, 권한 등)을 UserDetails 객체로 전달받는다.
-
AuthenticationProvider는 UserDetails 객체를 전달받은 이후 실제 사용자의 입력 정보와 UserDetails 객체를 가지고 인증을 시도한다.
7~10. 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandler를 실행한다. 실패 시 AuthenticationFilureHandler를 실행한다.
로그인
AuthenticationFilter
, DelegatingFilterProxy
우선 클라이언트(브라우저)로 부터 요청(Request)이 오면, 그 요청은 ApplicationFilter 객체들로 먼저 가게 된다.
DispatcherServlet에 도달하기 전이다.
ApplicationFilter들을 거치다가 DelegatingFilterRegistrationBean 이라는 필터를 만나게 된다.
이 필터는 DelegatingFIlterProxy라는 클래스로 만들어진 스프링 빈을 등록시켜주는 역할을 한다.
Filter
스프링 부트에서는 @EnableAutoConfiguration
어노테이션을 이용해서
SecurityFilterAutoConfiguration 클래스를 로드하고 디폴트로 이름이 SpringSecurityFilterChain 빈을 등록해준다.
이때 스프링 시큐리티가 만든 DelegatingFilterProxy 클래스인 SpringSecurityFilterChain 이라는 이름의 스프링 빈을 등록하고
이후에는 이 DelegatingFilterProxy(SpringSecurityFilterChain)가 필터로 동작하게 된다.
DelegatingFilterProxy가 처리를 위임하는 필터 클래스는 FilterChainProxy다.
이 클래스 내부에 체인으로 등록된 필터를 순서대로 수행하는 것이다.
DelegatingFIlterProxy ▶️ FIlterChainProxy ▶️ List 구조
SpringSecurityFilterChain은 스프링에서 보안과 관련된 여러 필터 리스트를 갖고 있는 객체로 필터 리스트를 순회하면서 필터링한다.
필터 리스트는 AuthenticationFilter들이고, 스프링 시큐리티가 자동으로 생성한다.
SpringSecurityFilterChain이라는 필터가 갖고 있는 필터 중 하나로 UsernamePasswordAuthenticationFilter라는 필터가 있다.
이 필터는 ID/Password를 이용한 인증을 담당하는 필터다.
OAuth 2.0
그렇다면 OAuth 2.0을 이용한 인증을 하려면 어떻게 동작할까?
UsernamePasswordAuthenticationFilter는 OAuth 2.0 인증을 할 수 없으니 인증되지 않은 채로 다음 필터로 넘어간다.
그 후 OAuth2ClientAuthenticationProcessingFilter라는 필터에서 OAuth 2.0을 이용한 인증이 진행된다.
이 필터로 OAuth를 쓴다고 설정하면 스프링 시큐리티가 설정해준다.
여러 필터를 거치며 인증이 완료되면 해당 요청(Request)은 인증된 요청이 되는 것이다.
요청에 대한 인증은 UsernamePasswordAuthenticationFilter가 담당한다.
public Authentication attempAuthentication(HttpServletRequest request, HttpServletResponse response) {
...
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
attempAuthentication(request, response)
메소드를 보면 요청으로부터
username, password를 얻어오고
그 값으로 UsernamePasswordAuthenticationToken(Authentication)을 생성한다.
그 다음 참조하고 있던 AuthenticationManager(구현체: ProviderManager)에게 인증을 진행하도록 위임한다.
여기서 UsernamePasswordAuthenticationToken은 Authentication 인터페이스의 구현체다.
Authentication의 구현체만 AuthenticationManager 인증 과정을 수행할 수 있다.
그렇다면 개발자가 스프링 시큐리티를 이용해 인증 절차를 만들려면?
AuthenticationProvider 인터페이스를 구현해서 ProviderManager가
그 클래스의 객체에게 인증을 위임하도록 하면 개발자가 원하는 인증 처리를 할 수 있게 된다.
참고
- https://mangkyu.tistory.com/76
- https://jeong-pro.tistory.com/205
- https://coding-start.tistory.com/153