f389d652d6
- fix/sso-refresh-session-restore 브랜치의 변경 배경과 목적 정리 - refreshToken 기반 SSO 세션 복원과 토큰 응답 보강 내용을 문서화 - 검증 포인트와 frontend 계약 변화를 함께 정리
221 lines
8.2 KiB
Markdown
221 lines
8.2 KiB
Markdown
# 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로 반환하도록 보강한 변경**입니다.
|