1
0
forked from baron/baron-sso
Files
baron-sso/docs/consent_loop_fix_report.md

5.2 KiB

Consent 반복 노출 문제 해결 보고서

1. 개요

Gitea 등 RP(Relying Party) 로그인 시, 사용자가 이미 권한에 동의했음에도 불구하고 재로그인할 때마다 Consent(권한 동의) 화면이 반복적으로 노출되는 문제를 해결했습니다. 이 문서는 해당 문제를 해결하기 위해 수정된 파일, 함수, 그리고 변경된 동작 흐름을 기술합니다.

2. 수정된 파일 및 함수

A. 백엔드 서비스 계층

  • 파일: backend/internal/service/hydra_admin_service.go
  • 함수: AcceptConsentRequest, AcceptLoginRequest
  • 변경 내용: Hydra에 동의 및 로그인 정보를 저장할 때 유효 기간(remember_for)을 연장.

B. 백엔드 핸들러 계층

  • 파일: backend/internal/handler/auth_handler.go
  • 함수: GetConsentRequest
  • 변경 내용: Hydra로부터 받은 동의 요청 정보에 skip: true 플래그가 있는 경우, 화면 데이터를 반환하는 대신 자동 승인 프로세스를 수행하도록 로직 추가.

C. 프론트엔드 (UserFront)

  • 파일: userfront/lib/features/auth/presentation/consent_screen.dart
  • 함수: _fetchConsentInfo
  • 변경 내용: 백엔드 API 응답에 redirectTo 필드가 포함된 경우, UI 렌더링을 건너뛰고 즉시 해당 URL로 리다이렉트하도록 수정.

3. 상세 수정 로직 및 동작 흐름

3.1. 동의 기억 기간 연장 (Backend Service)

기존에는 동의 정보가 1시간(3600) 동안만 유지되어, 1시간 후 재로그인 시 다시 동의 화면이 나타났습니다. 이를 30일(2592000)로 늘려 사용자의 편의성을 높였습니다.

// backend/internal/service/hydra_admin_service.go

func (s *HydraAdminService) AcceptConsentRequest(...) {
    // ...
    payload := map[string]interface{}{
        // ...
        "remember":       true,
        "remember_for":   2592000, // 수정 전: 3600 (1시간) -> 수정 후: 30일
    }
    // ...
}

3.2. 자동 승인(Skip) 로직 구현 (Backend Handler)

Hydra는 사용자가 이전에 동의한 기록이 유효하다면 skip: true 플래그를 보냅니다. 백엔드는 이 신호를 감지하여 사용자 개입 없이 동의 절차를 완료해야 합니다.

[수정된 흐름]

  1. GetConsentRequest 호출 시 Hydra로부터 consentRequest 정보를 받아옴.
  2. consentRequest.Skiptrue인지 확인.
  3. True인 경우 (자동 승인):
    • Kratos에서 사용자 신원(Identity) 조회.
    • 사용자 특성(Traits)을 기반으로 OIDC 클레임(sessionClaims) 생성.
    • Hydra.AcceptConsentRequest를 호출하여 승인 처리.
    • Hydra가 반환한 리다이렉트 URL(redirectTo)을 프론트엔드에 JSON으로 응답.
  4. False인 경우 (일반 진행):
    • 기존 로직대로 Consent 화면에 필요한 정보(클라이언트 이름, 스코프 목록 등)를 반환.
// backend/internal/handler/auth_handler.go

func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
    // ... Hydra 조회 ...

    // [추가된 로직] Skip 플래그 확인 및 자동 승인
    if consentRequest.Skip {
        // 1. 사용자 정보 조회
        identity, _ := h.KratosAdmin.GetIdentity(...)
        // 2. 클레임 생성
        sessionClaims := buildOidcClaimsFromTraits(...)
        // 3. Hydra 승인 요청
        acceptResp, _ := h.Hydra.AcceptConsentRequest(..., sessionClaims)
        
        // 4. 리다이렉트 URL 반환 (화면 생략)
        return c.JSON(acceptResp)
    }

    // ... 기존 화면 정보 반환 로직 ...
}

3.3. 즉시 리다이렉트 처리 (Frontend)

프론트엔드는 백엔드의 응답을 확인하여, 화면을 그릴지 아니면 바로 다른 페이지로 이동할지 결정합니다.

[수정된 흐름]

  1. ConsentScreen 진입 시 _fetchConsentInfo 실행.
  2. 백엔드 API(GET /consent) 호출.
  3. 응답 데이터(info)에 redirectTo 필드가 있는지 확인.
  4. 존재하는 경우: webWindow.redirectTo를 통해 즉시 이동. (UI 렌더링 중단)
  5. 없는 경우: 받은 정보를 바탕으로 권한 동의 UI(체크박스, 버튼 등) 렌더링.
// userfront/lib/features/auth/presentation/consent_screen.dart

Future<void> _fetchConsentInfo() async {
    final info = await AuthProxyService.getConsentInfo(...);
    
    // [추가된 로직] 리다이렉트 URL 존재 시 즉시 이동
    if (info['redirectTo'] != null) {
        webWindow.redirectTo(info['redirectTo']);
        return;
    }

    // ... UI 렌더링 준비 ...
}

4. 최종 동작 시나리오

  1. 최초 로그인:
    • Hydra skip: false -> 백엔드가 화면 정보 반환 -> 프론트엔드가 Consent UI 노출 -> 사용자 동의 -> 백엔드가 remember_for: 30일로 승인 처리.
  2. 재로그인 (30일 이내):
    • Hydra skip: true 반환.
    • 백엔드 GetConsentRequest가 이를 감지하고 내부적으로 AcceptConsentRequest 수행.
    • 백엔드가 프론트엔드에 { "redirectTo": "https://gitea..." } 응답.
    • 프론트엔드는 화면을 그리지 않고 즉시 Gitea로 이동.
    • 결과: 사용자는 동의 화면을 보지 않고 로그인 완료.