포스트

[Project A] 인증 도메인 기술 기획서

1. 목적

SpringBoot 기반 프로젝트의 인증 도메인 구현.

OAuth2, PKCE, JWT를 적용해 보안성 높은 인증 시스템을 목표로 한다.


2. 주요 기능

1) OAuth2 인증 지원

  • Google OAuth2
  • Kakao OAuth2
  • PKCE(Proof Key for Code Exchange) 방식으로 보안성 강화

2) JWT 토큰 기반 인증

  • Access Token: API 요청 시 활용되는 비교적 짧은 유효기간을 지니는 토큰
  • Refresh Token: AccessToken 재발급 시 활용되는 비교적 긴 유효기간을 지니는 토큰
  • 쿠키 기반 토큰 저장

3) 보안 기능

  • CSRF 공격 방지 (State 매개변수)
  • 토큰 자동 갱신
  • 안전한 로그아웃

3. 시스템 아키텍처

1) 계층 구조

1
2
3
4
5
6
7
8
9
┌─────────────────────┐
│   Presentation      │  ← API Controller, Request/Response DTO
├─────────────────────┤
│   Application       │  ← Service, Repository Interface
├─────────────────────┤
│   Domain            │  ← Business Logic, Domain Model
├─────────────────────┤
│   Infrastructure    │  ← External Client, JPA Entity, Redis, RepositoryImpl
└─────────────────────┘

2) 주요 컴포넌트

LayerComponentDescription
PresentationAuthenticationControllerREST API 엔드포인트 구현체
ApplicationAuthenticationService인증 비즈니스 로직 처리
ApplicationOAuth2UrlServiceOAuth2 URL 생성
DomainJwtProvider/JwtResolverJWT 토큰 생성/검증
DomainOAuth2ContextServiceOAuth2 인증 Context 생성
DomainOAuth2TokenServiceOAuth2 토큰 획득
DomainOAuth2UserProfileServiceOAuth2 유저 프로필 획득
DomainCodeChallengeServiceCodeChallenge 생성
InfrastructureGoogleTokenClientGoogle OAuth2 API 호출
InfrastructureKakaoTokenClientKakao OAuth2 API 호출

4. 인증 플로우

1) OAuth2 로그인 프로세스

sequenceDiagram
    participant Client
    participant API
    participant OAuth2Provider
    participant Redis
    participant DB

    Client->>API: 1. OAuth2 URL 요청 (code_verifier, redirect_path)
    API->>Redis: 2. OAuth2Context 저장 (state, code_verifier, redirect_path)
    API->>Client: 3. OAuth2 URL 반환 (state 포함)
    
    Client->>OAuth2Provider: 4. 사용자 인증 및 권한 동의
    OAuth2Provider->>Client: 5. Authorization Code 반환 (code, state): http://localhost:3000/auth/callback/{provider}
    
    Client->>API: 6. 로그인 요청 (code, state)
    API->>Redis: 7. OAuth2Context 조회 및 검증
    API->>OAuth2Provider: 8. Access Token 요청 (code, code_verifier)
    OAuth2Provider->>API: 9. Access Token 반환
    API->>OAuth2Provider: 10. 사용자 프로필 요청
    OAuth2Provider->>API: 11. 사용자 프로필 반환
    
    API->>DB: 12. 회원 정보 조회/생성
    API->>API: 13. JWT 토큰 생성 (Access + Refresh)
    API->>Redis: 14. Refresh Token 저장
    API->>Client: 15. 로그인 성공 (쿠키로 토큰 전달)

2) 토큰 갱신 프로세스

sequenceDiagram
    participant Client
    participant API
    participant Redis
    participant DB

    Client->>API: 1. 토큰 갱신 요청 (Refresh Token)
    API->>API: 2. Refresh Token 검증
    API->>Redis: 3. 저장된 Refresh Token 조회
    API->>API: 4. 토큰 일치 검증
    API->>DB: 5. 회원 정보 조회
    API->>API: 6. 새로운 Access Token 생성
    API->>Redis: 7. Refresh Token 사용 횟수 증가
    API->>Client: 8. 새로운 Access Token 반환

5. 데이터 모델

1) JWT Claims 구조

Access Token Claims

1
2
3
4
5
6
7
{
  "id": "회원 ID (Long)",
  "nickname": "회원 닉네임",
  "profileImage": "프로필 이미지 URL",
  "email": "이메일 주소",
  "authorities": ["ROLE_NORMAL"]
}

Refresh Token Claims

1
2
3
{
  "id": "회원 ID (Long)"
}

2) Redis 저장 데이터

OAuth2Context (임시 저장)

1
2
3
4
5
6
7
8
{
  "state": "CSRF 방지용 랜덤 문자열",
  "provider": "GOOGLE | KAKAO",
  "codeVerifier": "PKCE code verifier",
  "redirectPath": "클라이언트 리다이렉트 경로",
  "expiresAt": "만료 시간 (5분)",
  "ttl": "Redis TTL (초)"
}

RefreshToken

1
2
3
4
5
{
  "memberId": "회원 ID",
  "expiresAt": "만료 시간",
  "ttl": "Redis TTL (초)"
}

6. 보안 정책

1) PKCE (Proof Key for Code Exchange)

  • Code Verifier: 클라이언트가 생성하는 43-128자의 랜덤 문자열
  • Code Challenge: Code Verifier의 SHA256 해시값 (Base64 URL 인코딩)
  • Authorization Code Interception Attack 방지

2) State 매개변수

  • CSRF 공격 방지
  • 32바이트 랜덤 값을 Base64 URL 인코딩
  • 5분 만료 시간 설정

3) JWT 토큰 정책

  • Access Token: 만료 시간 30분
  • Refresh Token: 만료 시간 30일
  • HttpOnly, Secure, SameSite=None 쿠키 설정

4) 토큰 저장 정책

  • Access Token: 메모리 내 저장 (쿠키)
  • Refresh Token: Redis에 저장 (회원 ID 기준)
  • 로그아웃 시 모든 토큰 무효화

7. API 명세

1) OAuth2 URL 생성

1
2
3
4
5
6
7
8
POST /auth/oauth2/url
Content-Type: application/json

{
  "provider": "GOOGLE",
  "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
  "redirectPath": "/home"
}

응답:

1
2
3
4
5
6
{
  "url": "https://accounts.google.com/oauth/authorize?client_id=...",
  "state": "BjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
  "provider": "GOOGLE",
  "expiresAt": "2024-01-15T10:05:00"
}

2) OAuth2 로그인

1
2
3
4
5
6
7
8
POST /auth/oauth2/login
Content-Type: application/json

{
  "provider": "GOOGLE",
  "code": "4/0AX4XfWjJKZ...",
  "state": "BjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}

응답:

1
2
3
4
5
6
7
8
9
{
  "memberInfo": {
    "id": 1,
    "nickname": "홍길동#1234",
    "profileImage": "member/profile/image/01234567.jpg",
    "email": "user@example.com"
  },
  "redirectPath": "/home"
}

3) 로그아웃

1
2
POST /auth/logout
Cookie: access-token={access_token}

4) 토큰 갱신

1
2
POST /auth/token/refresh
Cookie: refresh-token={refresh_token}

8. 설정 및 환경변수

1) OAuth2 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app:
  oauth2:
    google:
      client-id: ${GOOGLE_CLIENT_ID}
      client-secret: ${GOOGLE_CLIENT_SECRET}
      authorization-url: https://accounts.google.com/o/oauth2/v2/auth
      token-url: https://oauth2.googleapis.com/token
      user-info-url: https://www.googleapis.com/oauth2/v2/userinfo
      redirect-uri: http://localhost:3000/auth/callback/google
      scope: email
    kakao:
      client-id: ${KAKAO_CLIENT_ID}
      client-secret: ${KAKAO_CLIENT_SECRET}
      authorization-url: https://kauth.kakao.com/oauth/authorize
      token-url: https://kauth.kakao.com/oauth/token
      user-info-url: https://kapi.kakao.com/v2/user/me
      redirect-uri: http://localhost:3000/auth/callback/kakao
      scope: account_email

2) JWT 설정

1
2
3
4
5
6
7
8
9
10
app:
  jwt:
    access-token:
      token-key: access-token
      secret-key: ${JWT_ACCESS_SECRET}
      expires-in: 1800 # 30분
    refresh-token:
      token-key: refresh-token
      secret-key: ${JWT_REFRESH_SECRET}
      expires-in: 2592000 # 30일

9. 예외 처리

1) OAuth2 관련 예외

예외 코드설명HTTP 상태
OAUTH-001지원하지 않는 OAuth2 제공자400
OAUTH-002Code Challenge 생성 실패500
OAUTH-003OAuth2 Context를 찾을 수 없음404
OAUTH-004OAuth2 제공자에서 토큰 획득 실패500
OAUTH-005OAuth2 제공자에서 사용자 프로필 획득 실패500

2) 인증/인가 관련 예외

예외 코드설명HTTP 상태
ACT-001Access Token을 찾을 수 없음401
ACT-002유효하지 않은 Access Token401
ACT-003만료된 Access Token401
RFT-001Refresh Token을 찾을 수 없음404
RFT-002유효하지 않은 Refresh Token400
RFT-003만료된 Refresh Token400
RFT-004Refresh Token 불일치400

10. 기술 스택

1) Backend Framework

  • Spring Boot 3.2.2
  • Spring Security 6
  • Spring Data JPA
  • Spring Data Redis

2) 외부 연동

  • OpenFeign: OAuth2 Provider API 호출
  • JWT: jjwt 라이브러리 사용

3) 데이터 저장소

  • Redis: 세션 및 임시 데이터 저장
  • MySQL: 회원 정보 저장

11. 확장 계획

1) 추가 OAuth2 제공자 지원

  • GitHub OAuth2
  • Naver OAuth2
  • Facebook OAuth2

2) 보안 강화

  • JWT 토큰 블랙리스트 관리
  • 비정상적인 로그인 시도 감지
  • 디바이스 기반 인증

3) 모니터링

  • 로그인 성공/실패 통계
  • 토큰 사용 패턴 분석
  • 보안 이벤트 로깅

12. 참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.