@@ -0,0 +1,95 @@
+package nextstep.security.authentication;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Set;
+import nextstep.app.domain.Member;
+import nextstep.app.domain.MemberRepository;
+import nextstep.security.authentication.OAuth2ClientProperties.Provider;
+import nextstep.security.authentication.OAuth2ClientProperties.Registration;
+import nextstep.security.context.HttpSessionSecurityContextRepository;
+import nextstep.security.context.SecurityContextHolder;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.filter.GenericFilterBean;
+
+public class OAuth2AuthenticationFilter extends GenericFilterBean {
+
+  private static final String REQUEST_URI = "/login/oauth2/code";
+
+    private final OAuth2ClientProperties oAuth2ClientProperties;
+    private final OAuth2AccessTokenClient oAuth2AccessTokenClient;
+    private final MemberRepository memberRepository;
+    private final OAuth2UserInfoClient oAuth2UserInfoClient;
+
+  public OAuth2AuthenticationFilter(OAuth2ClientProperties oAuth2ClientProperties,
+                                    MemberRepository memberRepository,
+                                    OAuth2AccessTokenClient oAuth2AccessTokenClient,
+                                    OAuth2UserInfoClient oAuth2UserInfoClient) {
+    this.oAuth2AccessTokenClient = oAuth2AccessTokenClient;
+    this.oAuth2UserInfoClient = oAuth2UserInfoClient;
+    this.oAuth2ClientProperties = oAuth2ClientProperties;
+    this.memberRepository = memberRepository;
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain filterChain) throws IOException, ServletException {
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+    String requestURI = httpRequest.getRequestURI();
+    String method = httpRequest.getMethod();
+
+    String registrationSource = null;
+    if (requestURI.startsWith(REQUEST_URI)) {
+      registrationSource = getRegistrationId(requestURI);
+    }
+
+    if (registrationSource != null && method.equals(HttpMethod.GET.name())) {
+      Registration registration = oAuth2ClientProperties.getRegistrations().get(registrationSource);
+      Provider provider = oAuth2ClientProperties.getProviders().get(registrationSource);
+
+      String code = httpRequest.getParameter("code");
+
+      String accessToken = oAuth2AccessTokenClient.getAccessToken(code, registration, provider);
+
+      OAuth2UserInfo userInfo = oAuth2UserInfoClient.getUserInfo(accessToken, provider);
+
+      String email = userInfo.getEmail();
+      Member member = memberRepository.findByEmail(email)
+          .orElse(new Member(
+              email, "", userInfo.getName(), userInfo.getPictureUrl(), Set.of("USER")
+          ));
+
+      UsernamePasswordAuthenticationToken authentication =
+          UsernamePasswordAuthenticationToken.authenticated(
+              member.getEmail(), null, member.getRoles());
+
+      SecurityContextHolder.getContext().setAuthentication(authentication);
+
+      httpRequest.getSession(true).setAttribute(
+          HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
+          SecurityContextHolder.getContext()
+      );
+
+      httpResponse.sendRedirect("/");
+      return;
+    }
+
+    filterChain.doFilter(request, response);
+  }
+
+  private String getRegistrationId(String requestURI) {
+    for (String registrationId : oAuth2ClientProperties.getRegistrations().keySet()) {
+      if (requestURI.contains(registrationId)) {
+        return registrationId;
+      }
+    }
+    return null;
+  }
+}

리뷰: > 장점으로는 결국 구현체에 의존하지 않는 구조로 변경되기 때문에, 결합도를 낮추고 유연하게 확장해 나갈 수 있을 것 같습니다!
또한 TestDouble 구현하기도 훨 수월할 것 같아요!
재연님이 생각하시는 이거 외에도 얻을 수 있는 중요 이점이 있으실까요?
( 반영은 [c887083](https://github.com/next-step/spring-security-oauth2/pull/7/commits/c8870831fea1cb685a7614a49c33229242b8e442) 로 진행했습니다.)

맞습니다. 결합도를 낮출 수 있다는 점이 가장 큰 장점일 것 같아요.
추가로, Spring의 DI 컨테이너를 활용하면 객체의 생명주기를 프레임워크가 관리해 주기 때문에 객체 생성 및 관리 부담도 줄어들 것 같습니다 ㅎㅎ