From f389d652d6b3263cddc7bb34137506a2153ed0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A7=84=EA=B8=B0=EC=A0=95?= Date: Thu, 9 Apr 2026 13:00:56 +0900 Subject: [PATCH] =?UTF-8?q?[docs]=20SSO=20=EC=84=B8=EC=85=98=20=EB=B3=B5?= =?UTF-8?q?=EC=9B=90=20=EB=B8=8C=EB=9E=9C=EC=B9=98=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix/sso-refresh-session-restore 브랜치의 변경 배경과 목적 정리 - refreshToken 기반 SSO 세션 복원과 토큰 응답 보강 내용을 문서화 - 검증 포인트와 frontend 계약 변화를 함께 정리 --- docs/sso-refresh-session-restore.md | 220 ++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/sso-refresh-session-restore.md diff --git a/docs/sso-refresh-session-restore.md b/docs/sso-refresh-session-restore.md new file mode 100644 index 0000000..93b03e2 --- /dev/null +++ b/docs/sso-refresh-session-restore.md @@ -0,0 +1,220 @@ +# fix/sso-refresh-session-restore 설명 문서 + +## 1. 문서 목적 + +이 문서는 backend 프로젝트 `api`의 `fix/sso-refresh-session-restore` 브랜치에서 반영한 **SSO 세션 복원 및 토큰 응답 보강** 변경을 설명합니다. + +핵심 목적은 다음 두 가지입니다. + +- `ALIST_SSO` 쿠키가 없더라도, 유효한 `refreshToken`이 있으면 SSO 세션을 복원할 수 있게 한다. +- frontend direct API 흐름에서 필요한 `accessToken` 값을 `/auth/access`, `/auth/refresh` 응답 body로도 내려준다. + +--- + +## 2. 배경 문제 + +기존 흐름에서는 SSO 관련 엔드포인트가 주로 `ALIST_SSO` 쿠키를 기준으로 로그인 상태를 판단했습니다. + +이 구조에서는 다음 문제가 있었습니다. + +1. 브라우저에 `refreshToken`은 남아 있지만 `ALIST_SSO` 쿠키가 사라진 경우 + - `/sso/loginChecked` + - `/sso/authorize` + - `/auth/access` + 에서 로그인 상태를 복원하지 못했습니다. + +2. frontend가 direct API 흐름으로 변경되면서 + - `POST /auth/access` + - `POST /auth/refresh` + 응답 body의 `accessToken`을 직접 사용하도록 바뀌었는데, + backend 응답은 cookie만 세팅하고 body에는 토큰을 충분히 내려주지 않았습니다. + +즉, **refreshToken 기반 세션 복원**과 **frontend 계약(body accessToken)** 이 동시에 필요했습니다. + +--- + +## 3. 변경 대상 파일 + +이 브랜치의 핵심 변경 파일은 아래 7개입니다. + +- `src/main/java/com/alist/api/modules/auth/AuthController.java` +- `src/main/java/com/alist/api/modules/auth/SsoController.java` +- `src/main/java/com/alist/api/modules/auth/dto/SsoAuthorizeDto.java` +- `src/main/java/com/alist/api/modules/auth/service/AuthService.java` +- `src/main/java/com/alist/api/modules/auth/service/SsoService.java` +- `src/main/java/com/alist/api/modules/auth/vo/LoginTokenVo.java` +- `src/main/resources/mapper/auth/LoginMapper.xml` + +--- + +## 4. 핵심 변경 요약 + +### 4-1. `refreshToken` 기반 SSO 세션 복원 추가 + +`SsoService`에 아래 역할이 추가되었습니다. + +- `loginChecked(String ssoSessionId, String refreshToken)` +- `resolveOrRestoreSsoSessionId(String ssoSessionId, String refreshToken)` +- `restoreSsoSessionIfNeeded(String ssoSessionId, String refreshToken)` +- `loadSsoSession(String ssoSessionId)` + +동작 방식은 아래와 같습니다. + +1. 먼저 기존 `ALIST_SSO` 쿠키 값(`ssoSessionId`)이 있으면 그대로 사용합니다. +2. `ssoSessionId`가 없으면 `refreshToken`으로 사용자 토큰 정보를 조회합니다. +3. 유효한 사용자가 확인되면 + - 기존 최신 SSO 세션이 있으면 TTL을 연장해서 재사용하고 + - 없으면 Redis에 새로운 SSO 세션을 다시 생성합니다. +4. 이후 `loginChecked`, `authorize`, `access`는 복원된 SSO 세션 기준으로 정상 동작합니다. + +즉, **SSO 쿠키가 비어 있어도 refreshToken만 유효하면 공통 로그인 상태를 다시 살릴 수 있게 변경**되었습니다. + +--- + +### 4-2. `/auth/access` 응답 강화 + +`AuthController.access()` 변경 내용: + +- `@CookieValue(name = "refreshToken", required = false)`를 추가하여 refreshToken을 입력으로 받습니다. +- `ssoService.loginChecked(ssoSessionId, refreshToken)`을 사용해 SSO 세션을 복원 가능하게 했습니다. +- 세션 복원 결과에 `ssoSessionId`가 있으면 `ALIST_SSO` 쿠키를 다시 발급합니다. +- 응답 body에 아래 값을 포함합니다. + - `isAccessToken` + - `userIdx` + - `userId` + - `userRole` + - `accessToken` + +즉, `/auth/access`는 이제 **토큰 쿠키 발급 + frontend용 JSON body accessToken 제공**을 같이 수행합니다. + +--- + +### 4-3. `/auth/refresh` 응답 강화 + +`AuthController.refresh()` 변경 내용: + +- 기존처럼 `accessToken`, `refreshToken` 쿠키는 재발급합니다. +- 추가로 응답 body에 아래 값을 내려줍니다. + - `refreshed: true` + - `accessToken` + +이 변경으로 frontend는 refresh 성공 후 cookie에만 의존하지 않고, **응답 body의 accessToken으로 즉시 service-local 토큰을 다시 기록**할 수 있습니다. + +--- + +### 4-4. `/sso/loginChecked` 개선 + +`SsoController.loginChecked()` 변경 내용: + +- `refreshToken` cookie를 함께 입력으로 받습니다. +- `ssoService.loginChecked(ssoSessionId, refreshToken)`을 사용합니다. +- access/refresh token 존재 여부를 기존처럼 함께 반환합니다. +- SSO 세션이 복원되면 `ALIST_SSO` 쿠키를 다시 써줍니다. + +즉, `/sso/loginChecked`는 더 이상 단순 조회가 아니라 **필요 시 세션 복원까지 수행하는 확인 엔드포인트**가 되었습니다. + +--- + +### 4-5. `/sso/authorize` 개선 + +`SsoController.authorize()`와 `SsoService.authorize()` 변경 내용: + +- `refreshToken` cookie를 함께 전달받아 세션 복원에 사용합니다. +- authorize 성공 시 `SsoAuthorizeDto`에 `ssoSessionId`를 실어주고, +- controller에서 해당 값을 기준으로 `ALIST_SSO` 쿠키를 재발급합니다. + +즉, 사용자가 이미 refreshToken을 가지고 있다면 **SSO 쿠키가 비어 있어도 authorize 진입 시 다시 공통 로그인 상태를 회복**할 수 있습니다. + +--- + +### 4-6. refreshToken 조회용 Auth 계층 보강 + +`AuthService`와 DB 조회 쪽에는 아래 보강이 들어갔습니다. + +- `findUserTokenByRefreshToken(String refreshToken)` 추가 +- `LoginTokenVo`에 `userIdx`, `userId` 필드 추가 +- `LoginMapper.xml`의 `selectUserTokenByUserTokenIdx`가 `user_idx`, `user_id`, `user_role`, `refresh_token`을 함께 조회 + +이 변경은 SSO 세션 복원 시, 단순 token 유효성 확인을 넘어서 **어떤 사용자의 세션을 복구해야 하는지 식별하기 위해 필요**합니다. + +--- + +## 5. 변경 후 기대 동작 + +### 시나리오 A. `ALIST_SSO` 없음 + `refreshToken` 유효 + +- `POST /auth/access` + - SSO 세션 복원 성공 + - `ALIST_SSO` 재발급 + - `accessToken` / `refreshToken` 재발급 + - body에 `accessToken` 포함 + +### 시나리오 B. `GET /sso/loginChecked` + +- 기존에는 로그인 false로 끝날 수 있었던 상황에서 +- 이제 refreshToken이 유효하면 loggedIn true로 복원 가능 + +### 시나리오 C. `GET /sso/authorize` + +- 기존에는 `ALIST_SSO` 없으면 `/login`으로 fallback +- 이제 refreshToken으로 세션 복원 가능하면 정상 authorize 진행 + +### 시나리오 D. `POST /auth/refresh` + +- cookie 재발급뿐 아니라 body에도 `accessToken` 제공 +- frontend가 바로 service-local accessToken을 다시 쓸 수 있음 + +--- + +## 6. frontend와의 계약 변화 + +이 브랜치는 frontend의 direct API 흐름과 맞물려 있습니다. + +frontend는 현재 아래 계약을 기대합니다. + +- `POST /auth/access` 응답 body에 `accessToken` +- `POST /auth/refresh` 응답 body에 `accessToken` + +따라서 이 브랜치 변경은 단순 backend 내부 개선이 아니라, +**frontend의 direct login / callback / refresh 흐름을 안정적으로 지원하는 계약 변경**이기도 합니다. + +--- + +## 7. 운영상 의미 + +이 변경을 적용하면 다음이 좋아집니다. + +- SSO 쿠키 유실 상황에서 세션 복원력이 높아짐 +- cross-domain authorize 진입 시 로그인 유지가 더 안정적임 +- frontend가 refresh 후 토큰 재반영을 더 단순하게 처리할 수 있음 +- `ALIST_SSO`와 `refreshToken`을 각각의 역할에 맞게 유지하면서도 사용자 체감 로그인 끊김을 줄일 수 있음 + +--- + +## 8. 검증 포인트 + +배포 또는 MR 검토 시 아래를 확인하는 것이 좋습니다. + +1. `POST /auth/access` + - `ALIST_SSO` 없이 `refreshToken`만 있을 때 200 또는 정상 복원되는지 + - response body에 `accessToken`이 포함되는지 + +2. `POST /auth/refresh` + - response body에 `accessToken`이 포함되는지 + +3. `GET /sso/loginChecked` + - `refreshToken`만으로 `loggedIn=true`가 가능한지 + +4. `GET /sso/authorize` + - `ALIST_SSO`가 없더라도 refreshToken 기반으로 redirect가 이어지는지 + +5. Redis 상태 + - `alist:sso:userIdx:*` + - `alist:sso:session:*` + 키가 복원/갱신되는지 + +--- + +## 9. 한 줄 요약 + +`fix/sso-refresh-session-restore` 브랜치는 **refreshToken만 남아 있는 상황에서도 SSO 세션을 복원하고, `/auth/access` 및 `/auth/refresh`가 frontend가 바로 사용할 수 있는 `accessToken`을 응답 body로 반환하도록 보강한 변경**입니다.