전체 흐름 개요
-
사용자가 프론트엔드에서 "구글로 로그인" 버튼 클릭:
-
프론트엔드가 /oauth2/authorization/google로 요청을 보냄.
-
-
백엔드에서 필터 체인 실행:
-
Spring Security의 필터 체인이 요청을 처리.
-
OAuth2AuthorizationRequestRedirectFilter가 요청을 처리하고 구글로 리다이렉트.
-
-
구글 로그인 페이지로 이동:
-
사용자가 구글에서 로그인.
-
-
구글에서 콜백 요청:
-
구글에서 백엔드의 콜백 URL(예: /login/oauth2/code/google)로 리다이렉트.
-
백엔드에서 다시 필터 체인 실행.
-
-
인증 성공 후 처리:
-
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에서 설정된 필터 체인을 기반으로 주요 필터를 나열하면:
-
SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
-
CsrfFilter: CSRF 보호 (비활성화됨).
-
OAuth2AuthorizationRequestRedirectFilter: OAuth2 인증 요청 처리.
-
TokenAuthenticationFilter: 커스텀 JWT 토큰 검증 필터.
-
OAuth2LoginAuthenticationFilter: OAuth2 콜백 처리.
-
FilterSecurityInterceptor: URL별 인증/인가 검사.
단계별 필터 실행
-
SecurityContextPersistenceFilter:
-
역할: SecurityContextHolder에서 SecurityContext를 로드하거나 새로 생성.
-
동작:
-
이 앱은 SessionCreationPolicy.STATELESS라 세션을 사용하지 않음.
-
SecurityContextHolder를 초기화하지만, 세션이 없으니까 빈 상태로 유지.
-
-
결과: 다음 필터로 넘어감.
-
-
CsrfFilter:
-
역할: CSRF 토큰 검사.
-
동작:
-
http.csrf().disable()로 비활성화되어 있음.
-
아무 동작 없이 다음 필터로 넘어감.
-
-
-
OAuth2AuthorizationRequestRedirectFilter:
-
역할: /oauth2/authorization/{provider} 요청을 처리.
-
동작:
-
요청 URL이 /oauth2/authorization/google이므로 이 필터가 동작.
-
인증 요청 생성:
-
ClientRegistrationRepository에서 구글의 설정(클라이언트 ID, 시크릿, 스코프 등)을 가져옴.
-
OAuth2AuthorizationRequest 객체를 생성 (리다이렉트 URI, 스코프, 상태(state) 등 포함).
-
-
상태 저장:
-
authorizationRequestRepository (OAuth2AuthorizationRequestBasedOnCookieRepository)를 사용해서 OAuth2AuthorizationRequest를 쿠키에 저장.
-
쿠키 이름: oauth2_auth_request.
-
이 상태는 CSRF 방지와 리다이렉트 후 원래 요청 복원용.
-
-
리다이렉트 URL 생성:
-
구글의 인증 엔드포인트로 리다이렉트 URL 생성.
-
-
리다이렉트 응답:
-
HTTP 302 응답을 클라이언트(브라우저)로 반환.
-
Location 헤더에 구글의 인증 URL 포함.
-
-
-
중요:
-
이 필터가 리다이렉트 응답을 보내기 때문에, 필터 체인의 이후 필터들은 실행되지 않음.
-
즉, TokenAuthenticationFilter, OAuth2LoginAuthenticationFilter, FilterSecurityInterceptor는 실행되지 않고 바로 반환.
-
-
필터 체인 실행 중단
-
OAuth2AuthorizationRequestRedirectFilter가 리다이렉트 응답(302)을 보냈으므로, 필터 체인은 여기서 종료됩니다.
-
반환:
-
클라이언트(브라우저)에게 302 Found 응답.
-
이 시점에서 실행된 필터
-
SecurityContextPersistenceFilter → CsrfFilter (비활성화) → OAuth2AuthorizationRequestRedirectFilter.
-
실행되지 않은 필터:
-
TokenAuthenticationFilter, OAuth2LoginAuthenticationFilter, FilterSecurityInterceptor.
-
3. 구글 로그인 페이지로 이동
-
브라우저가 OAuth2AuthorizationRequestRedirectFilter에서 받은 리다이렉트 응답을 처리.
-
사용자가 구글 로그인 페이지에서 이메일과 비밀번호를 입력하고 로그인.
구글에서 인증
-
구글에서 사용자가 로그인하면, 구글은 앱의 리다이렉트 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의 필터 체인이 다시 실행됩니다.
단계별 필터 실행
-
SecurityContextPersistenceFilter:
-
SecurityContextHolder 초기화.
-
세션이 없으니까 빈 상태로 유지.
-
다음 필터로 넘어감.
-
-
CsrfFilter:
-
비활성화되어 있으므로 아무 동작 없이 넘어감.
-
-
OAuth2AuthorizationRequestRedirectFilter:
-
이 필터는 /oauth2/authorization/{provider} 패턴에만 반응.
-
현재 요청은 /login/oauth2/code/google이므로 이 필터는 동작하지 않고 넘어감.
-
-
TokenAuthenticationFilter:
-
역할: 요청 헤더에서 JWT 토큰을 확인해서 인증 처리.
-
동작:
-
이 요청은 로그인 콜백 요청이라 JWT 토큰이 없음.
-
토큰이 없으니까 아무 동작 없이 다음 필터로 넘어감.
-
-
-
OAuth2LoginAuthenticationFilter:
-
역할: /login/oauth2/code/{provider} 요청을 처리.
-
동작:
-
요청 URL이 /login/oauth2/code/google이므로 이 필터가 동작.
-
상태 검증:
-
authorizationRequestRepository에서 쿠키에 저장된 OAuth2AuthorizationRequest를 가져옴.
-
요청의 state 파라미터와 비교해서 CSRF 공격 여부 확인.
-
-
액세스 토큰 요청:
-
요청: POST /token (클라이언트 ID, 시크릿, 코드, 리다이렉트 URI 포함).
-
응답: 액세스 토큰과 리프레시 토큰.
-
사용자 정보 조회:
-
요청: GET /userinfo (액세스 토큰 포함).
-
응답: 사용자 정보 (이메일, 이름 등).
-
사용자 정보 처리:
-
userInfoEndpoint().userService(oauth2UserCustomService) 설정에 따라 Oauth2UserCustomService를 호출.
-
Oauth2UserCustomService가 사용자 정보를 DB에 저장하거나 업데이트.
-
-
인증 객체 생성:
-
OAuth2AuthenticationToken을 생성해서 SecurityContextHolder에 저장.
-
-
인증 성공 처리:
-
successHandler(oAuth2SuccessHandler()) 설정에 따라 OAuth2SuccessHandler를 호출.
-
-
-
필터 체인 실행 중단
-
OAuth2LoginAuthenticationFilter가 인증을 처리하고 OAuth2SuccessHandler를 호출했으므로, 필터 체인은 여기서 종료됩니다.
-
이 시점에서 실행된 필터:
-
SecurityContextPersistenceFilter → CsrfFilter (비활성화) → 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);
}
-
동작:
-
리프레시 토큰 생성 및 저장.
-
액세스 토큰 생성.
-
targetUrl 생성: /articles?token=<accessToken>.
-
인증 관련 쿠키 제거.
-
/articles?token=<accessToken>으로 리다이렉트.
-
반환
-
클라이언트(브라우저)에게 302 Found 응답.
-
Location: /articles?token=abc123.
6. 프론트엔드로 리다이렉트
-
브라우저가 /articles?token=<accessToken>으로 이동.
-
프론트엔드가 URL에서 token 파라미터를 읽어서 액세스 토큰을 저장.
-
이후 API 요청 시 헤더에 Authorization: Bearer <accessToken>을 포함.
7. 전체 흐름 정리 (백엔드 기준)
첫 번째 요청: /oauth2/authorization/google
-
필터 체인:
-
SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
-
CsrfFilter: 비활성화.
-
OAuth2AuthorizationRequestRedirectFilter:
-
인증 요청 생성 및 쿠키 저장.
-
구글로 리다이렉트 응답(302) 반환.
-
-
-
실행 중단:
-
TokenAuthenticationFilter 이후 필터는 실행되지 않음.
-
-
구글로 리다이렉트:
-
브라우저가 구글 로그인 페이지로 이동.
-
구글 로그인
-
사용자가 구글에서 로그인.
-
구글에서 /login/oauth2/code/google로 콜백 요청.
두 번째 요청: /login/oauth2/code/google
-
필터 체인:
-
SecurityContextPersistenceFilter: SecurityContextHolder 초기화.
-
CsrfFilter: 비활성화.
-
OAuth2AuthorizationRequestRedirectFilter: 스킵.
-
TokenAuthenticationFilter: 토큰 없으므로 스킵.
-
OAuth2LoginAuthenticationFilter:
-
구글에 액세스 토큰 요청 (HTTP 요청).
-
구글에 사용자 정보 요청 (HTTP 요청).
-
Oauth2UserCustomService 호출.
-
OAuth2SuccessHandler 호출.
-
-
-
OAuth2SuccessHandler:
-
토큰 생성 및 리다이렉트 (/articles?token=<accessToken>).
-
8. 추가: 구글과의 HTTP 요청 시점
-
첫 번째 요청 (/oauth2/authorization/google):
-
백엔드가 직접 구글에 HTTP 요청을 보내는 건 아님.
-
OAuth2AuthorizationRequestRedirectFilter가 브라우저를 구글로 리다이렉트(302 응답).
-
브라우저가 구글의 인증 엔드포인트로 이동.
-
-
두 번째 요청 (/login/oauth2/code/google):
-
OAuth2LoginAuthenticationFilter가 구글에 HTTP 요청을 보냄:
-
결론
-
첫 번째 요청 (/oauth2/authorization/google):
-
OAuth2AuthorizationRequestRedirectFilter까지 실행.
-
구글로 리다이렉트 응답(302) 반환 → 필터 체인 중단.
-
-
구글 로그인:
-
사용자가 구글에서 로그인.
-
-
두 번째 요청 (/login/oauth2/code/google):
-
OAuth2LoginAuthenticationFilter까지 실행.
-
구글에 HTTP 요청(액세스 토큰, 사용자 정보).
-
OAuth2SuccessHandler 실행 → /articles로 리다이렉트.
-