배너 이미지

OAuth2 인증 로직 및 Google 기반 구현

2025. 4. 27. 00:30·CS공부/인프라

🎯 목표

  1. OAuth2 인증 로직에 대한 기본 개념과 동작 흐름 정리
    • OAuth2 핵심 요소(Authorization Server, Resource Server, Client, Token 등) 이해
    • Authorization Code Grant 방식의 인증 흐름 단계별 정리
  2. Google OAuth2를 활용한 프론트엔드-백엔드 연동 실습
    • 프론트엔드에서 Authorization 요청 → Redirect 응답 처리 → 백엔드로 Authorization Code 전달
    • 백엔드(Spring Boot WebFlux)에서 Authorization Code로 Access Token과 ID Token 교환
    • ID Token 검증 및 사용자 인증 처리
  3. 보안 고려사항 및 실무 적용 팁 제공
    • CSRF 방지를 위한 state 파라미터 사용
    • ID Token 서명 검증 및 안전한 토큰 저장 방식 안내
    • HTTPS 강제 사용 및 흔한 오류 대응 전략 정리

✅ 핵심 개념 및 정의

용어 설명
OAuth2 권한 위임(Authorization) 표준 프로토콜
Authorization Server 클라이언트에게 접근 토큰을 발급하는 서버
Resource Server 보호된 자원을 제공하는 API 서버
Client 보호된 자원에 접근하려는 애플리케이션
Access Token 자원 서버 접근을 위한 단기 사용 토큰
Refresh Token 만료된 Access Token을 재발급하기 위한 토큰
ID Token 사용자 인증 정보를 담은 토큰 (OIDC 확장)

🔍 동작 흐름 및 구조

  1. 클라이언트 등록
    Google Cloud Console에서 OAuth 2.0 클라이언트 ID/Secret 발급
  2. 사용자 인증 요청 (Authorization Code Grant)
  3. GET https://accounts.google.com/o/oauth2/v2/auth ?client_id={CLIENT_ID} &redirect_uri={REDIRECT_URI} &response_type=code &scope=openid%20email%20profile &state={STATE}
  4. 사용자 인증 및 승인 후 리디렉션
    GET {REDIRECT_URI}?code={AUTH_CODE}&state={STATE}
  5. 프론트엔드: code + state 검증 후 백엔드로 전달
  6. 백엔드: 토큰 교환 요청
  7. POST https://oauth2.googleapis.com/token Content-Type: application/x-www-form-urlencoded client_id={CLIENT_ID} &client_secret={CLIENT_SECRET} &code={AUTH_CODE} &grant_type=authorization_code &redirect_uri={REDIRECT_URI}
  8. Access Token + ID Token 수신
  9. 백엔드: ID Token 검증 및 사용자 인증
    • ID Token은 JWT 포맷
    • RS256 서명 검증 필요 (Google public key 이용)
  10. (선택) UserInfo 엔드포인트 추가 조회
  11. JWT 발급 또는 세션 관리 후 프론트엔드에 전달

🔗 프론트엔드 → 백엔드 연동 로직

(React 예시)

import React, { useEffect } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';

const CLIENT_ID = 'YOUR_CLIENT_ID';
const REDIRECT_URI = 'https://yourapp.com/oauth/callback';
const STATE_KEY = 'oauth_state';

export function LoginButton() {
  const state = uuidv4();
  sessionStorage.setItem(STATE_KEY, state);

  const authUrl = \`https://accounts.google.com/o/oauth2/v2/auth?client_id=\${CLIENT_ID}&redirect_uri=\${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=openid%20email%20profile&state=\${state}\`;

  return <a href={authUrl}>Google 로그인</a>;
}

export function OAuthCallback() {
  const { search } = useLocation();
  const history = useHistory();

  useEffect(() => {
    const params = new URLSearchParams(search);
    const code = params.get('code');
    const state = params.get('state');
    const savedState = sessionStorage.getItem(STATE_KEY);

    if (state !== savedState) {
      alert('State mismatch detected! Possible CSRF attack.');
      return;
    }

    if (code) {
      fetch('/api/auth/google', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ code }),
      })
      .then(res => res.json())
      .then(() => history.replace('/'));
    }
  }, [search, history]);

  return <div>로그인 중...</div>;
}

🛠 백엔드 구현 예시 (Spring Boot WebFlux)

@RestController
@RequestMapping("/api/auth")
public class OAuthController {

    @Value("${oauth.clientId}")
    private String clientId;

    @Value("${oauth.clientSecret}")
    private String clientSecret;

    @Value("${oauth.redirectUri}")
    private String redirectUri;

    private final WebClient webClient;

    public OAuthController(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.build();
    }

    @PostMapping("/google")
    public Mono<ResponseEntity<?>> googleAuth(@RequestBody Map<String, String> body) {
        String code = body.get("code");

        return webClient.post()
            .uri("https://oauth2.googleapis.com/token")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .body(BodyInserters.fromFormData("code", code)
                .with("client_id", clientId)
                .with("client_secret", clientSecret)
                .with("redirect_uri", redirectUri)
                .with("grant_type", "authorization_code"))
            .retrieve()
            .bodyToMono(Map.class)
            .flatMap(tokenResponse -> {
                String idToken = (String) tokenResponse.get("id_token");
                Map<String, Object> userInfo = JwtUtil.parseAndValidateIdToken(idToken);
                String jwt = JwtUtil.createToken(userInfo);
                Map<String, Object> result = new HashMap<>();
                result.put("accessToken", jwt);
                result.put("profile", userInfo);
                return Mono.just(ResponseEntity.ok(result));
            });
    }
}
# application.yml
oauth:
  clientId: YOUR_CLIENT_ID
  clientSecret: YOUR_CLIENT_SECRET
  redirectUri: https://yourapp.com/oauth/callback

spring:
  webflux:
    base-path: /api

🚧 Security Tip

  • state 파라미터 검증 필수 (CSRF 방지)
  • HTTPS 적용 필수
  • ID Token 서명 검증 필수
  • Access Token은 가능하면 HttpOnly Secure 쿠키에 저장

⚠️ 흔한 이슈 및 해결 전략

이슈 원인 해결 방법
redirect_uri_mismatch 등록된 URI와 요청 URI 불일치 Google Console 등록 URI 정확히 설정
invalid_grant Authorization Code 재사용 또는 만료 코드 1회용 사용, 즉시 교환
invalid_client Client ID/Secret 오류 환경변수로 안전하게 관리

💡 학습 정리

  • OAuth2 = 권한 위임 프로토콜
  • Authorization Code Grant 권장
  • 프론트→백엔드→Google 인증 흐름 구현
  • state 검증, ID Token 검증, HTTPS 적용 필수

'CS공부 > 인프라' 카테고리의 다른 글

Prometheus & Grafana 기반 Springboot 모니터링 구축(with 401에러)  (0) 2025.05.08
모니터링 시스템 비교: ELK vs Prometheus & Grafana  (0) 2025.05.08
동기 vs 비동기 & 메시지 브로커 개념 정리  (0) 2025.05.01
GitHub 웹훅 기반 Jenkins CI/CD 파이프라인 설계  (0) 2025.04.23
Docker로 Redis 클러스터 구축하기  (0) 2025.04.22
'CS공부/인프라' 카테고리의 다른 글
  • 모니터링 시스템 비교: ELK vs Prometheus & Grafana
  • 동기 vs 비동기 & 메시지 브로커 개념 정리
  • GitHub 웹훅 기반 Jenkins CI/CD 파이프라인 설계
  • Docker로 Redis 클러스터 구축하기
quokkaST
quokkaST
  • quokkaST
    stquokka
    quokkaST
    • 개발자 (77)
      • n8n (2)
      • CS공부 (46)
        • Java & Spring (15)
        • 인프라 (7)
        • 운영체제 & 시스템 (9)
        • 기타 CS지식 (7)
        • 네트워크 (6)
        • 데이터베이스 (2)
      • 알고리즘 (16)
      • 프로젝트 (8)
        • 감정&금융챗봇 (8)
      • 리팩토링 (5)
        • horong (5)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
상단으로

티스토리툴바