@@ -0,0 +1,69 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.core.context.SecurityContext;
+import nextstep.security.core.context.SecurityContextHolder;
+import nextstep.security.exception.AuthenticationException;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+public abstract class AbstractAuthProcessingFilter extends OncePerRequestFilter {
+    private final AuthenticationManager authenticationManager;
+
+    protected AbstractAuthProcessingFilter(final AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
+        if (!match(request)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
+        if (isAlreadyAuthenticated()) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
+        Authentication authRequest = makeAuthentication(request);
+
+        Authentication authentication = authenticate(authRequest);
+
+        saveAuthentication(request, response, authentication);
+
+        successAuthentication(request, response, filterChain);
+    }
+
+    private static boolean isAlreadyAuthenticated() {
+        return SecurityContextHolder.getContext().getAuthentication() != null;
+    }
+
+    private Authentication authenticate(final Authentication authRequest) {
+        Authentication authentication = this.authenticationManager.authenticate(authRequest);
+        if (!authentication.isAuthenticated()) {
+            throw new AuthenticationException();
+        }
+
+        return authentication;
+    }
+
+    abstract boolean match(final HttpServletRequest request);
+
+    private void saveAuthentication(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) {
+        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+        securityContext.setAuthentication(authentication);
+        SecurityContextHolder.setContext(securityContext);
+    }
+
+    abstract Authentication makeAuthentication(final HttpServletRequest request);
+
+    protected abstract void successAuthentication(final HttpServletRequest request, final HttpServletResponse response,
+                                                  final FilterChain filterChain) throws ServletException, IOException;
+}

리뷰: 기존 스프링 시큐리티 코드가 레거시 형태고 두 필터 모두 doFilter를 호출하는 것이 맞다고 생각했는데요.
그 근거로 만약 로그인 후 이후 요청은 처리하지 않는 것이 해당 클래스의 역할이라면 UsernamePasswordAuthenticationFilter와 BasicAuthenticationFilter 두 클래스 모두 doFilter를 호출하지 않아야 한다고 생각했습니다.
근데 생각해보니.. UsernamePasswordAuthenticationFilter 필터는 로그인을 위한 필터고 BasicAuthenticationFilter는 토큰을 검증하는 역할이라 제가 잘못 생각한 것 같네요 ㅠㅠ


DM으로 나눈 내용이지만 히스토리 용으로 남겨둡니다~