forked from baron/baron-sso
139 lines
5.7 KiB
Markdown
139 lines
5.7 KiB
Markdown
# RP 연동 해지(Consent Revoke) 기능 구현 가이드
|
|
|
|
## 1. 개요
|
|
사용자가 UserFront 대시보드의 '활동상황' 섹션에서 특정 서비스(RP)와의 연동(동의)을 직접 해지할 수 있는 기능입니다. 이 기능을 통해 사용자는 자신의 정보 제공 동의를 철회할 수 있으며, 이후 해당 서비스 재접속 시 다시 동의 화면을 거치게 됩니다.
|
|
|
|
## 2. 동작 흐름 (Workflow)
|
|
|
|
1. **사용자 요청 (Frontend)**:
|
|
* 대시보드 활동상황 카드에서 '연동 해지' 버튼을 클릭합니다.
|
|
* 확인 모달(Dialog)이 뜨고, 사용자가 '해지하기'를 확정합니다.
|
|
2. **API 호출 (Frontend -> Backend)**:
|
|
* UserFront는 백엔드 API `DELETE /api/v1/user/rp/linked/{client_id}`를 호출합니다.
|
|
* 이때, `AuthProxyService`는 현재 세션의 인증 토큰(Bearer Token)을 헤더에 포함하여 요청합니다.
|
|
3. **동의 철회 처리 (Backend -> Hydra)**:
|
|
* 백엔드는 요청을 수신하고, 요청자의 `subject`(사용자 ID)를 식별합니다.
|
|
* Ory Hydra Admin API를 호출하여 해당 `subject`와 `client_id`에 대한 모든 동의 세션(Consent Sessions)을 삭제합니다.
|
|
4. **UI 갱신 (Frontend)**:
|
|
* API 호출이 성공하면, 프론트엔드는 목록을 새로고침하지 않고 해당 카드의 상태를 즉시 '해지됨'으로 변경합니다.
|
|
* 해지된 카드는 흐릿하게(Opacity) 처리되며, 버튼이 비활성화되어 중복 요청을 방지합니다.
|
|
|
|
---
|
|
|
|
## 3. 백엔드 구현 상세 (Go)
|
|
|
|
### 파일: `backend/internal/handler/auth_handler.go`
|
|
|
|
#### 3.1 핸들러 구현: `RevokeLinkedRp`
|
|
프론트엔드의 삭제 요청을 받아 처리하는 진입점입니다.
|
|
|
|
```go
|
|
func (h *AuthHandler) RevokeLinkedRp(c *fiber.Ctx) error {
|
|
// 1. 파라미터 파싱
|
|
clientID := c.Params("id")
|
|
|
|
// 2. 사용자 식별 (Subject 조회)
|
|
subject, err := h.resolveConsentSubject(c)
|
|
if err != nil || subject == "" {
|
|
return fiber.NewError(fiber.StatusUnauthorized, "Authentication required")
|
|
}
|
|
|
|
// 3. 서비스 호출 (Hydra 연동)
|
|
if err := h.Hydra.RevokeConsentSessions(c.Context(), subject, clientID); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to revoke link")
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"status": "success",
|
|
"message": "Link revoked successfully",
|
|
})
|
|
}
|
|
```
|
|
|
|
### 파일: `backend/internal/service/hydra_admin_service.go`
|
|
|
|
#### 3.2 Hydra 연동: `RevokeConsentSessions`
|
|
실제 Hydra Admin API를 호출하여 동의 세션을 삭제하는 로직입니다.
|
|
|
|
* **API Endpoint**: `DELETE /admin/oauth2/auth/sessions/consent`
|
|
* **Query Params**: `subject={user_id}`, `client={client_id}`, `all=true`
|
|
|
|
```go
|
|
func (s *HydraAdminService) RevokeConsentSessions(ctx context.Context, subject, clientID string) error {
|
|
// ... (Hydra Client 초기화)
|
|
|
|
// Hydra API 호출
|
|
_, err := s.client.Admin.RevokeConsentSessions(ctx).
|
|
Subject(subject).
|
|
Client(clientID).
|
|
All(true). // 해당 클라이언트에 대한 모든 세션 삭제
|
|
Execute()
|
|
|
|
return err
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 프론트엔드 구현 상세 (Flutter)
|
|
|
|
### 파일: `userfront/lib/core/services/auth_proxy_service.dart`
|
|
|
|
#### 4.1 API 호출: `revokeLinkedRp`
|
|
**중요**: 401 인증 오류를 방지하기 위해 `AuthTokenStore`에서 토큰을 가져와 명시적으로 헤더에 추가하는 로직이 적용되었습니다.
|
|
|
|
```dart
|
|
static Future<void> revokeLinkedRp(String clientId) async {
|
|
// ... (URL 설정)
|
|
final url = Uri.parse('$baseUrl/api/v1/user/rp/linked/$clientId');
|
|
|
|
// 인증 헤더 구성 (401 오류 해결 핵심)
|
|
final useCookie = AuthTokenStore.usesCookie();
|
|
final token = AuthTokenStore.getToken();
|
|
final client = createHttpClient(withCredentials: useCookie);
|
|
|
|
final headers = <String, String>{
|
|
'Content-Type': 'application/json',
|
|
};
|
|
if (!useCookie && token != null) {
|
|
headers['Authorization'] = 'Bearer $token';
|
|
}
|
|
|
|
final response = await client.delete(url, headers: headers);
|
|
// ... (에러 핸들링)
|
|
}
|
|
```
|
|
|
|
### 파일: `userfront/lib/features/dashboard/presentation/dashboard_screen.dart`
|
|
|
|
#### 4.2 상태 관리 및 UI 로직
|
|
해지된 항목을 로컬 상태(`Set<String> _revokedClientIds`)로 관리하여, 불필요한 API 재조회 없이 즉각적인 UI 피드백을 제공합니다.
|
|
|
|
1. **상태 변수**:
|
|
```dart
|
|
final Set<String> _revokedClientIds = {}; // 이번 세션에서 해지한 ID 목록
|
|
```
|
|
2. **해지 핸들러 (`_onRevokeLink`)**:
|
|
* 사용자 확인 모달 표시.
|
|
* API 호출 성공 시 `setState`를 통해 `_revokedClientIds`에 ID 추가.
|
|
* `ScaffoldMessenger`로 "해지되었습니다" 알림 표시.
|
|
3. **UI 렌더링 (`_buildActivityCard`)**:
|
|
* `_revokedClientIds`에 포함된 ID인 경우:
|
|
* `Opacity(0.6)` 적용.
|
|
* 버튼 텍스트를 '해지됨'으로 변경하고 비활성화(`null`).
|
|
* 상태 라벨을 '비활성'으로 표시.
|
|
|
|
```dart
|
|
// UI 상태 결정
|
|
final isRevoked = _revokedClientIds.contains(rp.id);
|
|
|
|
// 카드 투명도 처리
|
|
final opaqueCard = Opacity(
|
|
opacity: item.isRevoked ? 0.6 : 1.0,
|
|
child: cardContent,
|
|
);
|
|
```
|
|
|
|
## 5. 요약
|
|
이 기능은 백엔드에서의 **정확한 사용자 식별 및 Hydra 세션 철회**와 프론트엔드에서의 **안전한 인증 처리 및 즉각적인 UI 피드백**이 결합되어 구현되었습니다. 특히 프론트엔드에서 연동 해지 시 목록에서 아예 사라지는 것이 아니라, '해지됨' 상태로 남겨두어 사용자가 자신의 행동(해지)을 명확히 인지할 수 있도록 UX를 고려했습니다.
|