@@ -0,0 +1,79 @@
+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);
+
+        if (!shouldContinueFilterChain()) {
+            return;
+        }
+
+        doFilter(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;
+
+    protected boolean shouldContinueFilterChain() {
+        return true;
+    }
+}

리뷰: 기존의 힌트 코드를 꼭 그대로 사용하지 않으셔도 되고, 문제가 있다면 수정하셔도 됩니다 ㅎㅎ 

Filter의 구조는 디자인패턴 중 하나인 연쇄 책임 패턴의 형태를 가지고 있는데요.
Filter는 다른 필터에 대한 정보를 모르는 상태에서 자신의 역할만 수행하고, 문제가 없다면 반드시 다음 필터로 위임하는 것이 정해진 규칙을 가지고 있습니다.
만약 구현 방식에 따라 doFilter() 호출 여부가 달라진다면, 설계를 다시 고민해볼 필요가 있을 것 같습니다!