백기선의 스프링 부트 개념과 활용 강의에 짧게 소개되어 있는 스프링 시큐리티 부분을 보고
사실 spring security는 WebSecurityConfigureAdapter를 그대로 사용하고 있다. Springboot가 기본적으로 제공하는 부분을 쓰고 있는 것. 그나마 Spring security가 제공하는건 UserDetails 부분이다. UserDetailsServiceAutoConfiguration은 자동으로 User를 만들어주는 부분인데 이 설정은 사실 AuthenticationManager 혹은 AuthenticationProvider 혹은 UserDetailsService가 없을 때 inmemory로 랜덤하게 유저를 생성해주는 것이다.
이 조건은@ConditionalOnClass 혹은 @ConditionalOnMissingBean과 같은 annotation으로 걸려있다. WebSecurityConfigurerAdapter에 기본적으로 설정되어 있는 configure method를 보면 모든 요청에 대해 권한을 요청하고, 기본적인 formLogin과 httpBasic을 사용하는 모습을 확인할 수 있다. 처음 Spring Security를 등록만 하고 아무 설정도 안했을 때 로그인 페이지가 뜨는 게 이 부분 때문이다. 내가 아무것도 모르고 하란대로 override해서 썼던 부분이 사실 이런거였다니...
사실 Spring Boot 가 설정해주는 Spring Security는 이게 거의 다인데 대부분 Spring Security를 사용하는 프로젝트들은 UserDetails 부분을 직접 구현해서 등록해야 하기 때문에 Spring boot가 Spring Security에 관해 직접 도와주는 부분은 별로 없게 된다고 한다. 나도 인증 서버를 만들 때 모두 Customizing했기 때문에 100프로 동감한다. 잘 만들어 두었지만 결국 하나도 손대지 않고 쓸수는 없는 느낌(?).
spring-security-test dependency를 추가하고 메소드나 클래스 위에 @WithMockUser annotation을 추가하면 가짜 유저로 테스트할 수 있다고 한다. 테스트 자체에 익숙하지 않은데 이번 프로젝트에서 TDD 입문하는게 목표다.
SecurityContextHolder는 Authentication을 담고있으며, 실제 인증은 AuthenticationManager가 담당한다. AuthenticationManager 인터페이스 안에는 authenticate method 한개밖에 없다. Authentication 객체를 받아 유효한지 확인해서 유효하다면 UserDatailsService가 리턴한 객체를 Principle 형태로 담고 있는 Authentication 객체를 리턴한다.
AuthenticationManager 구현체로는 ProvideManager를 이용한다. 대부분의 앱들이 Dao Interface를 쓴다.
먼저 AuthenticationProvider한테 위임을 하고, parent provider(AbstractUserDetailsAuthenticationProvider)가 UserDatailsService를 사용해서 인증을 해준다. retrieve user로 들어가면 DaoAuthenticationProvider로 들어와서 UserDatailsService, 즉 내가 설정해서 등록했던 custom UserDetailsService로 연결이 된다. 그 다음에는 계정이 잠겨있는지 등등 preAuthenticationChecks로 추가 확인을 한다. 그 후 유저를 cache하고 result로 Authentication 객체를 return한다. 이 result는 UsernamePasswordAuthenticationToken 객체인데, 이 객체의 principal에는 loadUserbyUsername 으로 return했던 User 객체이다. Authentication 안에 들어가는 Principal이라는 객체는 UserDetailsService에서 리턴한 그 객체라는 것이다. 그래서 SecurityContext가 이걸 가지게 된다.
Authentication autentication = SecurityHolder.getContext().getAuthentication()
로 authentication 객체를 꺼내면 authentication.getPrincipal(), getAuthorities(), getCredentials() 등 여러가지를 그대로 확인해 볼 수 있다.
디버깅! 알아보고 싶은 곳에 빨간 점 찍어놓고 내부 동작의 흐름을 그대로 따라가면서 확인할 수 있다. 보통 C/C++로 알고리즘 짜다가 막히거나 과제하다가 막혔는데 print하기 귀찮을 때만 디버깅을 했었다. 맨날 DIY로 만든 조잡한 함수들간에만 쓰다보니 디버깅에 대해 단순히 내가 생각한 그 값이 잘 들어갔나 확인하는 용도 외에는 생각해본 적이 없다. 그런데 잘 짜여진 Spring을 데려와 사용하는 지금은 다른 용도로 쓸 수 있을 것 같다. 점 찍어놓고 따라가다 보면 내부적으로 어떻게 구현이 되어있는지 하나하나 내 눈으로 확인할 수 있다. 우선 급한 개인 과제 2번을 마무리하고 나면 테스트와 함께 시간을 들여 디버깅으로 꼭 한번씩 따라가보는 습관을 들여야겠다. 사실 테스트와 디버깅을 하지 않고 내부 동작방식을 완전히 이해했다고 할 수 있는지 조금 부끄러워졌다. 코드 짜집러를 탈출하기 위해 당장 구현속도는 조금 느려지더라도 습관을 바꿔야겠다.
스프링 시큐리티 강의를 들어봐도 좋을 것 같은데 당장 완강하기에는 스프링부트 자체에 대한 개념을 잘 잡는게 더 급한 것 같다. 다음에 꼭 들어보는걸로...(미루기+1)