카테고리 없음

Oauth 흐름

ernest45 2025. 4. 1. 20:36
전체 흐름 개요
  1. 사용자가 프론트엔드에서 "구글로 로그인" 버튼 클릭:
    • 프론트엔드가 /oauth2/authorization/google로 요청을 보냄.
  2. 백엔드에서 필터 체인 실행:
    • Spring Security의 필터 체인이 요청을 처리.
    • OAuth2AuthorizationRequestRedirectFilter가 요청을 처리하고 구글로 리다이렉트.
  3. 구글 로그인 페이지로 이동:
    • 사용자가 구글에서 로그인.
  4. 구글에서 콜백 요청:
    • 구글에서 백엔드의 콜백 URL(예: /login/oauth2/code/google)로 리다이렉트.
    • 백엔드에서 다시 필터 체인 실행.
  5. 인증 성공 후 처리:
    • OAuth2SuccessHandler가 실행되어 토큰 발급 및 리다이렉트.
이제 각 단계를 백엔드 기준으로 자세히 따라가 보겠습니다.

1. 사용자가 프론트엔드에서 "구글로 로그인" 클릭
  • 상황:
    • 사용자가 /login 페이지에서 "구글로 로그인" 버튼을 클릭.
    • 프론트엔드가 /oauth2/authorization/google로 GET 요청을 보냄.
    • 예: <a href="/oauth2/authorization/google">구글로 로그인</a>.
백엔드에서 요청 수신
  • 요청: GET /oauth2/authorization/google.
  • Spring Security의 필터 체인이 실행됩니다.

2. Spring Security 필터 체인 실행 (첫 번째 요청: /oauth2/authorization/google)
Spring Security는 요청을 처리하기 위해 필터 체인을 실행합니다. 이 앱의 설정(WebOauthSecurityConfig)을 기준으로 필터 체인의 주요 필터와 동작을 단계별로 따라가 보겠습니다.
필터 체인 구성
WebOauthSecurityConfig에서 설정된 필터 체인을 기반으로 주요 필터를 나열하면:
  1. SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
  2. CsrfFilter: CSRF 보호 (비활성화됨).
  3. OAuth2AuthorizationRequestRedirectFilter: OAuth2 인증 요청 처리.
  4. TokenAuthenticationFilter: 커스텀 JWT 토큰 검증 필터.
  5. OAuth2LoginAuthenticationFilter: OAuth2 콜백 처리.
  6. FilterSecurityInterceptor: URL별 인증/인가 검사.
단계별 필터 실행
  1. SecurityContextPersistenceFilter:
    • 역할: SecurityContextHolder에서 SecurityContext를 로드하거나 새로 생성.
    • 동작:
      • 이 앱은 SessionCreationPolicy.STATELESS라 세션을 사용하지 않음.
      • SecurityContextHolder를 초기화하지만, 세션이 없으니까 빈 상태로 유지.
    • 결과: 다음 필터로 넘어감.
  2. CsrfFilter:
    • 역할: CSRF 토큰 검사.
    • 동작:
      • http.csrf().disable()로 비활성화되어 있음.
      • 아무 동작 없이 다음 필터로 넘어감.
  3. OAuth2AuthorizationRequestRedirectFilter:
    • 역할: /oauth2/authorization/{provider} 요청을 처리.
    • 동작:
      • 요청 URL이 /oauth2/authorization/google이므로 이 필터가 동작.
      • 인증 요청 생성:
        • ClientRegistrationRepository에서 구글의 설정(클라이언트 ID, 시크릿, 스코프 등)을 가져옴.
        • OAuth2AuthorizationRequest 객체를 생성 (리다이렉트 URI, 스코프, 상태(state) 등 포함).
      • 상태 저장:
        • authorizationRequestRepository (OAuth2AuthorizationRequestBasedOnCookieRepository)를 사용해서 OAuth2AuthorizationRequest를 쿠키에 저장.
        • 쿠키 이름: oauth2_auth_request.
        • 이 상태는 CSRF 방지와 리다이렉트 후 원래 요청 복원용.
      • 리다이렉트 URL 생성:
      • 리다이렉트 응답:
        • HTTP 302 응답을 클라이언트(브라우저)로 반환.
        • Location 헤더에 구글의 인증 URL 포함.
    • 중요:
      • 이 필터가 리다이렉트 응답을 보내기 때문에, 필터 체인의 이후 필터들은 실행되지 않음.
      • 즉, TokenAuthenticationFilter, OAuth2LoginAuthenticationFilter, FilterSecurityInterceptor는 실행되지 않고 바로 반환.
필터 체인 실행 중단
  • OAuth2AuthorizationRequestRedirectFilter가 리다이렉트 응답(302)을 보냈으므로, 필터 체인은 여기서 종료됩니다.
  • 반환:
이 시점에서 실행된 필터
  • SecurityContextPersistenceFilterCsrfFilter (비활성화) → OAuth2AuthorizationRequestRedirectFilter.
  • 실행되지 않은 필터:
    • TokenAuthenticationFilter, OAuth2LoginAuthenticationFilter, FilterSecurityInterceptor.

3. 구글 로그인 페이지로 이동
  • 브라우저가 OAuth2AuthorizationRequestRedirectFilter에서 받은 리다이렉트 응답을 처리.
  • 구글의 인증 URL(예: https://accounts.google.com/o/oauth2/auth?...)로 이동.
  • 사용자가 구글 로그인 페이지에서 이메일과 비밀번호를 입력하고 로그인.
구글에서 인증
  • 구글에서 사용자가 로그인하면, 구글은 앱의 리다이렉트 URI로 콜백 요청을 보냅니다.
  • 리다이렉트 URI는 application.yml에서 설정된 값:
    yaml
     
    spring:
      security:
        oauth2:
          client:
            registration:
              google:
                redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
  • 이 앱의 경우: /login/oauth2/code/google.
구글에서 보내는 콜백 요청
  • 요청: GET /login/oauth2/code/google?code=xxx&state=xxx.
    • code: 구글에서 발급한 인증 코드.
    • state: CSRF 방지용 상태 값 (쿠키에 저장된 값과 비교).

4. Spring Security 필터 체인 실행 (두 번째 요청: /login/oauth2/code/google)
구글에서 콜백 요청이 오면, Spring Security의 필터 체인이 다시 실행됩니다.
단계별 필터 실행
  1. SecurityContextPersistenceFilter:
    • SecurityContextHolder 초기화.
    • 세션이 없으니까 빈 상태로 유지.
    • 다음 필터로 넘어감.
  2. CsrfFilter:
    • 비활성화되어 있으므로 아무 동작 없이 넘어감.
  3. OAuth2AuthorizationRequestRedirectFilter:
    • 이 필터는 /oauth2/authorization/{provider} 패턴에만 반응.
    • 현재 요청은 /login/oauth2/code/google이므로 이 필터는 동작하지 않고 넘어감.
  4. TokenAuthenticationFilter:
    • 역할: 요청 헤더에서 JWT 토큰을 확인해서 인증 처리.
    • 동작:
      • 이 요청은 로그인 콜백 요청이라 JWT 토큰이 없음.
      • 토큰이 없으니까 아무 동작 없이 다음 필터로 넘어감.
  5. OAuth2LoginAuthenticationFilter:
    • 역할: /login/oauth2/code/{provider} 요청을 처리.
    • 동작:
      • 요청 URL이 /login/oauth2/code/google이므로 이 필터가 동작.
      • 상태 검증:
        • authorizationRequestRepository에서 쿠키에 저장된 OAuth2AuthorizationRequest를 가져옴.
        • 요청의 state 파라미터와 비교해서 CSRF 공격 여부 확인.
      • 액세스 토큰 요청:
        • 구글에서 받은 code를 사용해서 구글의 토큰 엔드포인트(예: https://oauth2.googleapis.com/token)에 HTTP 요청을 보냄.
        • 요청: POST /token (클라이언트 ID, 시크릿, 코드, 리다이렉트 URI 포함).
        • 응답: 액세스 토큰과 리프레시 토큰.
      • 사용자 정보 조회:
      • 사용자 정보 처리:
        • userInfoEndpoint().userService(oauth2UserCustomService) 설정에 따라 Oauth2UserCustomService를 호출.
        • Oauth2UserCustomService가 사용자 정보를 DB에 저장하거나 업데이트.
      • 인증 객체 생성:
        • OAuth2AuthenticationToken을 생성해서 SecurityContextHolder에 저장.
      • 인증 성공 처리:
        • successHandler(oAuth2SuccessHandler()) 설정에 따라 OAuth2SuccessHandler를 호출.
필터 체인 실행 중단
  • OAuth2LoginAuthenticationFilter가 인증을 처리하고 OAuth2SuccessHandler를 호출했으므로, 필터 체인은 여기서 종료됩니다.
  • 이 시점에서 실행된 필터:
    • SecurityContextPersistenceFilterCsrfFilter (비활성화) → OAuth2AuthorizationRequestRedirectFilter (스킵) → TokenAuthenticationFilter (스킵) → OAuth2LoginAuthenticationFilter.
  • 실행되지 않은 필터:
    • FilterSecurityInterceptor (필터 체인이 중단됨).

5. OAuth2SuccessHandler 실행
OAuth2LoginAuthenticationFilter가 인증 성공 후 OAuth2SuccessHandler를 호출합니다:
java
 
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
    User user = userService.findByEmail((String) oAuth2User.getAttributes().get("email"));

    // 리프레시 토큰 생성 -> 저장 -> 쿠키에 저장
    String refreshToken = tokenProvider.generateToken(user, REFRESH_TOKEN_DURATION);
    savaRefreshToken(user.getId(), refreshToken);
    addRefreshTokenToCookie(request, response, refreshToken);

    // 액세스 토큰 생성 -> 패스에 액세스 토큰 추가
    String accessToken = tokenProvider.generateToken(user, ACCESS_H_TOKEN_DURATION);
    String targetUrl = getTargetUrl(accessToken);

    // 인증 관련 설정값, 쿠키 제거
    clearAuthenticationAttributes(request, response);

    // 리다이렉트
    getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
  • 동작:
    1. 리프레시 토큰 생성 및 저장.
    2. 액세스 토큰 생성.
    3. targetUrl 생성: /articles?token=<accessToken>.
    4. 인증 관련 쿠키 제거.
    5. /articles?token=<accessToken>으로 리다이렉트.
반환
  • 클라이언트(브라우저)에게 302 Found 응답.
  • Location: /articles?token=abc123.

6. 프론트엔드로 리다이렉트
  • 브라우저가 /articles?token=<accessToken>으로 이동.
  • 프론트엔드가 URL에서 token 파라미터를 읽어서 액세스 토큰을 저장.
  • 이후 API 요청 시 헤더에 Authorization: Bearer <accessToken>을 포함.

7. 전체 흐름 정리 (백엔드 기준)
첫 번째 요청: /oauth2/authorization/google
  • 필터 체인:
    1. SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
    2. CsrfFilter: 비활성화.
    3. OAuth2AuthorizationRequestRedirectFilter:
      • 인증 요청 생성 및 쿠키 저장.
      • 구글로 리다이렉트 응답(302) 반환.
  • 실행 중단:
    • TokenAuthenticationFilter 이후 필터는 실행되지 않음.
  • 구글로 리다이렉트:
    • 브라우저가 구글 로그인 페이지로 이동.
구글 로그인
  • 사용자가 구글에서 로그인.
  • 구글에서 /login/oauth2/code/google로 콜백 요청.
두 번째 요청: /login/oauth2/code/google
  • 필터 체인:
    1. SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
    2. CsrfFilter: 비활성화.
    3. OAuth2AuthorizationRequestRedirectFilter: 스킵.
    4. TokenAuthenticationFilter: 토큰 없으므로 스킵.
    5. OAuth2LoginAuthenticationFilter:
      • 구글에 액세스 토큰 요청 (HTTP 요청).
      • 구글에 사용자 정보 요청 (HTTP 요청).
      • Oauth2UserCustomService 호출.
      • OAuth2SuccessHandler 호출.
  • OAuth2SuccessHandler:
    • 토큰 생성 및 리다이렉트 (/articles?token=<accessToken>).

8. 추가: 구글과의 HTTP 요청 시점
  • 첫 번째 요청 (/oauth2/authorization/google):
    • 백엔드가 직접 구글에 HTTP 요청을 보내는 건 아님.
    • OAuth2AuthorizationRequestRedirectFilter가 브라우저를 구글로 리다이렉트(302 응답).
    • 브라우저가 구글의 인증 엔드포인트로 이동.
  • 두 번째 요청 (/login/oauth2/code/google):

결론
  • 첫 번째 요청 (/oauth2/authorization/google):
    • OAuth2AuthorizationRequestRedirectFilter까지 실행.
    • 구글로 리다이렉트 응답(302) 반환 → 필터 체인 중단.
  • 구글 로그인:
    • 사용자가 구글에서 로그인.
  • 두 번째 요청 (/login/oauth2/code/google):
    • OAuth2LoginAuthenticationFilter까지 실행.
    • 구글에 HTTP 요청(액세스 토큰, 사용자 정보).
    • OAuth2SuccessHandler 실행 → /articles로 리다이렉트.