[Spring] Security 구조 파해치기

누구세요·2024년 10월 9일

Spring은 익숙해지지 않아서 그런지 모르겠는데 절차지향이나 객체지향과는 다르게 뭐랄까 코드가 다 흩어져서 둥둥 떠다니는 느낌 흐름을 못읽겠다ㅠㅠ

Spring Security - Filter Chain

Spring Security을 사용하면 Controller을 거치치 않고 로그인 절차를 할 수 있다고 한다. 아직 그렇게 해서 얻는게 뭔지는 이해 못했지만;; 아무튼 실제로 강의에서 만들어뒀던 ControllerService에서 로그인과 관련된 코드를 모두 주석처리 했음에도 잘 동작했다.
그런데 그럼 DB에 쿼리는 누가 날리고 있는거지??하는 생각이 들었다.

강의를 그냥 그러쿠나 끄덕끄덕하고 멍때렸더니 문제가 생겼다. 그렇게 삽질이 시작됐다.

// 필터 관리
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

WebSecurityCongig class에서 아래 두줄을 추가했으므로 필터 순서는
1. JwtAuthorizationFilter
2. JwtAuthenticationFilter
...???(중략)
3. UsernamePasswordAuthenticationFilter이다.

@Override
   protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
       log.info("doFilterInternal " + req.getRequestURI());
       String tokenValue = jwtUtil.getTokenFromRequest(req);

       if (StringUtils.hasText(tokenValue)) {
           // JWT 토큰 substring
           tokenValue = jwtUtil.substringToken(tokenValue);
           log.info(tokenValue);

           if (!jwtUtil.validateToken(tokenValue)) {
               log.error("Token Error");
               return;
           }

           Claims info = jwtUtil.getUserInfoFromToken(tokenValue);

           try {
               setAuthentication(info.getSubject());
           } catch (Exception e) {
               log.error(e.getMessage());
               return;
           }
       }

       filterChain.doFilter(req, res);
   }

로그인 버튼을 누르면 JwtAuthorizationFilter의 필터 부분으로 들어온다.
처음엔 토큰이 없으므로 JwtAuthenticationFilter 체인으로 넘어간다.

@Override
   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
       log.info("로그인 시도");
       try {
           LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);

           return getAuthenticationManager().authenticate(
                   new UsernamePasswordAuthenticationToken(
                           requestDto.getUsername(),
                           requestDto.getPassword(),
                           null
                   )
           );
       } catch (IOException e) {
           log.error(e.getMessage());
           throw new RuntimeException(e.getMessage());
       }
   }

doFilter가 아니라 이부분으로 넘어오는 것 같다. 여기서 입력된 아이디와 비밀번호를 받아서
authenticate에서 쿼리를 날린다고 한다.

아무튼간에 Detail어쩌구 하는 부분에서 쿼리를 날리니까 여기에 중단점을 걸었다.

JwtAuthenticationFilterauthenticate함수 안으로 들어가면

ProviderManager라는 곳에서 똑같은 이름의 함수를 호출한다. 또 타고 들어가면

retrieveUser라는 함수를 호출한다. 요기로 들어가면

오.. 익숙한 함수가 보인다. LoadUserByUsername함수로 들어가면

@Service
@Slf4j(topic = "UserDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("loadUserByUsername " + username);
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));

        return new UserDetailsImpl(user);
    }
}

@Bean에 등록해뒀던 UserDetailsService을 상속받아 구현한 UserDetailsServiceImpl에서 오버라이딩한 loadUserByUsername쪽으로 들어오는 모양이다.

그러니까 필터를 통과하면서 저 Proxy..?뭔가 안에서 연결해둔 Detail가서 유저 정보 뽑아오고 그걸로 로그인 검증을 하는 것 같다!

사실 아직도 두루뭉실..하고.. Spring을 잘 몰라서 @Bean이 거의 달아두면 아무튼간에 아무데서나 쓸 수 있는 마법의 Static처럼 보이는 상태라.. 반복하면서 이해하는 것 밖에 없는 것 같다.

0개의 댓글