본문 바로가기
Spring

사용자 요청부터 스프링 시큐리티까지 feat)OAuth2

by ernest45 2025. 4. 14.

목차

     

     

     

    스프링 시큐리티 6 기준!

     

     

     

     

     

     

    실제 스프링 시큐리티를 만지면서 너무 어렵고 깊이있게 파고들고 싶어서 좀 더 알아보는 흐름을 갖고 싶어서 써보겠다

     

    실제 유저가 스프링에 요청을 보내는 순간으로 부터 시작해서 스프링 시큐리티 작동 원리를 찾아보고

    일반적인 form 로그인과 Oauth의 경우까지 알아보자

     

     

     

     

    1. http 요청 시 흐름

     

     

    Client가 API 요청을 하면

    Web Application server(Java에서는 Tomcat) → Servlet(Java에서는 Dispatcher Servlet ) → Controller 

    순서로 요청이 전달되는데,

     

     Filter chain Web Application server Servlet 사이에서 작동한다.

     

    // 흐름
    HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러
    
    // 체인
    HTTP 요청 → WAS → 필터 → 필터2 → 필터3 → 서블릿 → 컨트롤러

     


    (톰캣과 Dispatcher Servlet사이에 존재하는 필터체인)

     

     

     

     

    2. FilterChain

     
    Servlet은 클라이언트 요청을 Dispatcher Servlet에 가져다 주기 전 여러 필터를 거치게 되는데
     
    그 필터들의 연결이 **FilterChain**

     

    **필터체인**은 요청을 처리하기 전에 미리 정해진 필터 통과. 

     

    필터는 여러 개가 체인처럼 연결돼 있어서, 요청이 하나씩 통과하면서 점검받는 구조(순서대로가 중요)

    각 필터는 doFilter 메서드를 통해 요청을 받아서 뭔가를 하고, 다음 필터나 서블릿으로 넘김.

     

    (그래서 모든 필터를 호출 후 doFilter을 메서드를 호출해 넘겼어야 했다)

     

     

     

     

     

    3. 스프링 시큐리티 실제 흐름

     

     

     
     
     
     

    흐름도

     
    로깅 필터 -> 인코딩 필터 -> DelegatingFilterProxy -> 커스텀 필터
     
     
     
    스프링 시큐리티를 설정을 했다면 서블릿 컨테이너에서 각필터가 진행되다가
     
     
    DelegatingFilterProxy가 호출될 때 스프링 컨텍스트에 있는 FilterChainProxy로 작업을 넘김

     

    쉽게 말하면, DelegatingFilterProxy는 호출만 하고,

    실제 보안 로직은 FilterChainProxy가 여러 필터를 불러서 처리!!

     

    적절한 인증/인가 작업 후 다시 커스텀필터를 호출 후 최종  Dispatcher Servlet mvc 동작!

     

     

     

     

     

    4. 스프링 시큐리티 필터 종류들(FilterChainProxy)

    FilterChainProxy는

    위 종류들을 갖고 있는 springSecurityFilter들을 요청에 맞게 호출함

     

     

    일반 세션 폼 로그인 기반 시   springSecurityFilter 중요한 필터 흐름 

     

    SecurityContextPersistenceFilter

    • 이전 요청에서 저장된 SecurityContext 복원(세션에서 인증 정보 가져오기) or
    • SecurityContextHolder 생애 주기를 관리(실제 인증빼고)
    • 호출 후 응답까지의 로컬쓰레드에 해제까지의 전반적인 흐름의 시작과 끝에 있음 

    try- context 저장,  finally- 쓰레드로컬에서 해제

     

    -세션 기반이라,  jwt 쓸꺼면 상관없다.

     

     

     

     

     

    security 5.7 이후로  @deprecated됨

    여기에 대해서도 조금 쓸 일이 많은 것 같긴 하다..

    무조건 세션에 저장을 안해도 된다는 뜻인 거 같음

    https://www.inflearn.com/community/questions/883957/5-7-%EC%9D%B4%ED%9B%84%EB%A1%9C-deprecated-%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4?srsltid=AfmBOop5w8r2NhU11uA5kbkr1gEOWWLghYjyYRjdTtZRq6m1QiT7epYs

     

     

     

    UsernamePasswordAuthenticationFilter ( 실제 핵심 로그인처리)

    • 로그인 폼에서 아이디/비밀번호를 받아 인증 처리
    • authenticaiton에 실제로 인증을 요청하는 핵심

    AuthenticationManager, AuthenticationProvider

     

    BasicAuthenticationFilter

    • HTTP Basic 인증(헤더에 인증 정보 넣는 방식) 처리.

     

    AnonymousAuthenticationFilter

    • 로그인 안 한 사용자를 "익명 사용자"로 설정.

    ExceptionTranslationFilter

    • 보안 예외(401, 403 등) 처리.
     

    FilterSecurityInterceptor

    • 최종 권한 체크.
     
     
    ** 필터는 순서가 중요하다. 인증 후에 인가해야하기에 **
     
     
    @EnableWebSecurity(debug = true)
    config 설정 파일에 @EnableWebSecurity(debug = true)를 할 경우 필터체인 순서를 볼 수 있음!
    무조건 개발환경에서만 해야한다!
     

     

     

     

     

    5. OAuth 의 경우

     

     

    OAuth2AuthorizationRequestRedirectFilter

     

     

    특정 uri에서 호출됨  :/oauth2/authorization/{provider}   

     

     ex) 구글의 예시: 

    /oauth2/authorization/google

      

    OAuth2 로그인 요청을 받아 사용자를 외부 제공자의 로그인 페이지로 리다이렉트시키는 필터.

     

     

     

    순서도

     

    1. 로그인 요청 시  인증 요청(Authorization Request)을 만듦

     

    2 .Authorization Request에는 (client_id,redirect_id, 스코프 등)이 포함

     

    3. 사용자를 제공자의 로그인 페이지로 리다이렉트 (예: 구글 로그인 화면).

     

     

    결과 !

     

    사용자가 구글 같은 제공자에서 로그인하면 인가 코드(authorization code)가 리다이렉트 URI로 돌아온다. 이 코드는 다음 필터가 처리!

     

     


     

     

     

    OAuth2LoginAuthenticationFilter 

     

    위에서 받은 인가 코드를 처리해 사용자 인증을 완료하고 SecurityContextHolder에 인증 정보를 설정하는 필터

     

    특정 uri에서 호출됨 /login/oauth2/code/*

     

    ex) 구글의 예시:  /login/oauth2/code/google 

    (내가 지정한 redirectURL)

     

     

    1. 위 코드에서 받아온 인증코드로 구글에게 제공자 정보에 접근 가능한 액세스 토큰 발행

     

    2. 액세스 토큰으로 사용자 실제 정보 받음

     

        OAuth2UserCustomService가 이 데이터를 처리 (예: DB 저장).

     

    블로그 만들기 -Oauth2구글에 요청이 자동으로 간다고?

    (실제 실행 메서드가 궁금하면  보고오자)

     

     

    3. OAuth2AuthenticationToken을 만들어 SecurityContextHolder에 저장.

     

    4.성공oAuth2SuccessHandler() 호출, 실패oAuth2failureHandler() 호출

     

     

    결과 !

    인증 성공 시 SecurityContextHolder에 사용자 정보가 채워지고, 내 핸들러가 JWT를 발급해 클라이언트에 준다. 이후 요청은 TokenAuthenticationFilter가 JWT로 인증 유지!

     

     

     

    usernamePasswordauthenticationfilter의 인증 과정을 거침

     

     

     

     

     

    위 두 필터가 OAuth의 핵심이다.
     
     

     

    스프링 시큐리티의 기본 필터체인에는 포함되어 있지 않음

    필터들은 OAuth 2.0 로그인 기능을 지원하기 위해 별도로 추가된 필터라서

     "spring-security-oauth2-client" 의존성 추가가 필수다.

     

     

    OAuth의 흐름도

    1. 요청 → DelegatingFilterProxyFilterChainProxy
    2. OAuth2AuthorizationRequestRedirectFilter 후 OAuth2LoginAuthenticationFilter로 인증 처리
    3. SecurityContext에 인증 정보 저장 → 디스패처 서블릿

     

     

     

     

     

     

    -- 질문 -- 

    HttpSessionOAuth2AuthorizationRequestRepository가 필요한 이유를 따라가다

    redirect 때문인 것을 알게 되었는데 그럼 왜 redirect 방식으로 oauth를 채택했나 궁금해졌다!

     

     

     

     

    인가 요청 저장소의 역할:
    세션이나 쿠키에 저장하는 건, 리다이렉트로 인해 HTTP 요청이 끊기니까 흐름을 이어가기 위한 필수 장치. AuthorizationRequestRepository(예: HttpSessionOAuth2AuthorizationRequestRepository)가 이 역할!
     
    난 jwt를 사용하기에 이 세션이 아닌 쿠키방식의 저장소를 커스텀했다
     

    3서버의 멀티스레드 처리
    웹 서버(WAS, 예: Tomcat)는 멀티스레드로 여러 클라이언트의 요청을 동시에 처리해. 네가 인용한 문맥에서 "서버는 아무것도 할 게 없지"라는 건, 특정 클라이언트의 OAuth 흐름에 대해선 콜백 요청이 올 때까지 기다리는 거지, 서버 전체가 멈춘다는 뜻은 아니야. 리다이렉트를 보내면:
    • 서버는 그 클라이언트의 요청을 끝내고 스레드를 해제해.
    • 다른 클라이언트의 요청(예: 다른 사용자의 페이지 조회, API 호출)을 계속 처리해.
    • 콜백 요청이 오면 새로운 스레드로 처리 시작.
    반면, 서버가 기다리려면 스레드가 계속 점유된 상태로 대기해야 해. 이건 서버의 확장성(scalability)을 망가뜨리고, 동시 접속자가 많아질수록 성능이 급격히 떨어져.
     
    5. OAuth의 리다이렉트 설계가 더 나은 이유
    OAuth 2.0이 리다이렉트를 선택한 건 우연이 아니야. 이 방식이 "기다리는" 방식보다 나은 이유를 정리해보면:
    • 효율성: 서버는 리다이렉트를 보낸 후 리소스를 해제하고 다른 요청을 처리해. 콜백 요청이 올 때만 다시 처리하면 되니까 확장성이 좋아.
    • 보안: 인가 요청을 세션이나 쿠키에 저장하고, state 파라미터로 CSRF 공격을 방지해. 기다리는 방식은 상태를 유지하기 어렵거나 새로운 보안 위험이 생길 수 있어.
    • 사용자 경험: 사용자는 익숙한 제공자 UI(구글 로그인 화면)에서 인증하고, 애플리케이션으로 자연스럽게 돌아와. 기다리는 방식은 사용자에게 "로딩 중" 같은 부자연스러운 UI를 보여줄 가능성이 커.
    • 표준화: OAuth 2.0은 전 세계적으로 표준화된 프로토콜이라, 구글, 페이스북, 깃허브 등 모든 제공자가 리다이렉트 기반으로 동작해. 기다리는 방식은 표준에서 벗어나서 호환성 문제 생김.

     

     

     

    나는 거의 바로 응답이 오니까 굳이 redirect로 다시 filter를 거쳐야 하나 싶었지만, 

    생각해보니 만약 연결이 끊긴다면? 계속 기다리느라 그 스레드는 비효율적으로 계속 대기해야한다는 뜻이다.

    다시 redirect 시 리소스를 해제하고 다른 요청 계속 처리 후 콜백 요청이 온다며 다시 처리하면 됨!

     

     

     

     

     

     

     

     

    https://docs.spring.io/spring-security/reference/servlet/architecture.html

     

    Architecture :: Spring Security

    The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like exploit protection,authentication, authorization, and more. The filters are executed in a speci

    docs.spring.io

    https://velog.io/@zini9188/Spring-Security-Filter%EC%99%80-FilterChain

     

    [Spring Security] Filter와 FilterChain

    서블릿 필터는 서블릿 기반 애플리케이션의 엔드포인트에 요청이 도달하기 전에 중간에 요청을 가로챈 후 어떤 처리를 할 수 있도록 해주는 JAVA의 컴포넌트필터의 처리가 완료되면 디스패처서

    velog.io

     https://roadj.tistory.com/15 

    https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter

    https://www.youtube.com/watch?v=02PavC_ZzQA

    'Spring' 카테고리의 다른 글

    Chain of Responsibility  (0) 2025.05.10
    JPA 1+N 문제 실습(1)  (0) 2025.04.12
    @transactional을 private메서드에 사용하고 싶다  (0) 2024.01.21
    @entity 빨간줄  (0) 2024.01.16