28 KiB
Baron SSO 로그인 화면 3가지 방식 절차 및 흐름 정리
작성일: 2026-06-22
1. 목적
Baron SSO 로그인 페이지에는 사용자가 선택할 수 있는 로그인 탭이 3개 있다.
- 비밀번호
- 로그인 링크
- QR 코드
본 문서는 각 탭이 어떤 상황에서 쓰이고, 사용자가 어떤 절차를 거치며, 내부 시스템에서는 어떤 데이터와 인증 상태가 이동하는지 설명한다.
2. 로그인 화면 기준 요약
| 탭 | 사용자 입력/행동 | 대표 Backend API | 인증 성격 | 적합한 상황 |
|---|---|---|---|---|
| 비밀번호 | 이메일 또는 휴대폰 번호 + 비밀번호 입력 | POST /api/v1/auth/password/login |
Kratos password 기반 직접 인증 | 가장 기본적인 로그인, 최초 로그인, 비밀번호를 알고 있는 경우 |
| 로그인 링크 | 이메일 또는 휴대폰 번호 입력 후 수신한 링크/코드 승인 | POST /api/v1/auth/enchanted-link/init -> poll, POST /api/v1/auth/magic-link/verify, POST /api/v1/auth/login/code/verify |
Baron pendingRef 기반 원격 승인 | 비밀번호 입력을 줄이고 싶거나, 다른 기기/채널로 본인 확인하려는 경우 |
| QR 코드 | 로그인 화면에 QR 표시, 이미 로그인된 기기에서 스캔/승인 | POST /api/v1/auth/qr/init -> poll -> approve |
교차 기기 승인 | PC에서 로그인하려는데 모바일 또는 다른 브라우저에 이미 로그인되어 있는 경우 |
핵심 차이는 다음과 같다.
- 비밀번호: 현재 로그인 화면에서 사용자가 직접 비밀번호를 증명한다.
- 로그인 링크: 현재 로그인 화면은 대기하고, 이메일/SMS/링크를 받은 쪽에서 승인을 수행한다.
- QR 코드: 현재 로그인 화면은 QR을 띄우고, 이미 로그인된 승인 기기가 해당 QR을 승인한다.
3. 공통 운영 경로
운영 환경에서는 사용자의 요청이 Backend, Kratos, Hydra로 직접 들어가지 않는다. 일반적으로 앞단의 L7 또는 Traefik, Baron Gateway Nginx, Oathkeeper를 거쳐 내부 서비스로 라우팅된다.
각 구성요소를 간단히 설명하면 다음과 같다.
| 구성요소 | 쉬운 역할 | 로그인 흐름에서 하는 일 |
|---|---|---|
| L7 또는 Traefik | 외부 출입구 / 1차 교통정리 | 사용자가 접속한 도메인과 HTTPS 요청을 받아 Baron SSO 쪽으로 넘긴다. 운영 환경에 따라 Traefik, 외부 L7 로드밸런서, Ingress, ALB 같은 장비가 이 역할을 한다. |
| Baron Gateway Nginx | Baron SSO 내부 안내 데스크 | 들어온 URL 경로를 보고 UserFront, Backend, Oathkeeper 중 어디로 보낼지 나눈다. 예를 들어 /api/*는 Backend, /oidc/*는 Oathkeeper/Hydra 쪽으로 보낸다. |
| Oathkeeper | Ory 앞단 보안 게이트 | /oidc/*, /auth/*처럼 Ory로 가는 요청을 받아 정책에 맞게 통과시키고, 내부 Ory 주소로 전달한다. 현재 흐름에서는 Hydra/Kratos 앞단의 보호 프록시 역할을 한다. |
| Ory Hydra | OIDC/OAuth2 인가 서버 | RP의 client_id, redirect_uri, scope, state, code_challenge를 확인하고, 로그인 필요 시 login_challenge를 만들어 UserFront 로그인 화면으로 보낸다. |
| UserFront | Baron SSO 로그인 화면 | 사용자가 보는 비밀번호, 로그인 링크, QR 코드 탭 화면이다. |
따라서 L7, Nginx, Oathkeeper는 사용자를 인증하는 주체라기보다 "요청을 올바른 내부 서비스로 안전하게 전달하는 앞단 계층"에 가깝다. 실제 로그인 요청 접수와 OIDC 판단은 Hydra가 하고, 비밀번호 검증과 세션 발급은 Backend와 Kratos가 처리한다.
중요한 점은 모든 요청이 Oathkeeper를 거치는 것은 아니라는 것이다. Oathkeeper는 주로 Ory Public endpoint 앞단에 붙어 있다. UserFront 화면 요청과 Baron Backend API 요청은 Nginx에서 각각 UserFront와 Backend로 직접 라우팅된다.
요청 종류별 경로는 다음과 같이 구분된다.
| 요청 종류 | 예시 | 실제 경로 | Oathkeeper 경유 여부 |
|---|---|---|---|
| 로그인 화면 요청 | /, /ko/login?login_challenge=... |
브라우저 -> L7 -> Nginx -> UserFront | 경유하지 않음 |
| Baron Backend API 요청 | /api/v1/auth/password/login, /api/v1/auth/qr/init |
UserFront -> L7 -> Nginx -> Backend | 경유하지 않음 |
| Ory Public 요청 | /oidc/oauth2/auth, /auth/* |
브라우저/RP -> L7 -> Nginx -> Oathkeeper -> Hydra/Kratos Public | 경유함 |
| Backend 내부 백채널 | GetLoginRequest, AcceptLoginRequest, Kratos 세션 발급 |
Backend -> Hydra Admin/Kratos API/Redis | 경유하지 않음 |
즉 로그인 화면이 표시될 때의 UserFront 요청은 Oathkeeper를 거치지 않는다. 반면 RP가 처음 보낸 /oidc/oauth2/auth 요청은 Ory Public endpoint이므로 Nginx에서 Oathkeeper로 보내고, Oathkeeper가 Hydra로 전달한다.
헷갈리기 쉬운 두 경로를 분리하면 다음과 같다.
로그인 화면 요청:
사용자 브라우저 -> L7 -> Baron Gateway Nginx -> UserFront
OIDC 인증 요청:
사용자 브라우저/RP -> L7 -> Baron Gateway Nginx -> Oathkeeper -> Hydra
로그인 버튼/API 요청:
UserFront -> L7 -> Baron Gateway Nginx -> Baron Backend
Backend 내부 완료 처리:
Baron Backend -> Hydra Admin API
다만 위 표는 "요청 종류별 경로"를 나눈 것이고, 사용자가 RP에 접속한 뒤 Baron SSO 로그인 화면에 도달하기까지의 실제 시간 순서는 아래처럼 하나의 흐름으로 이어진다.
3.1 RP 접속부터 Baron SSO 로그인 화면 도달까지
사용자가 RP 업무시스템에 접속했는데 RP 세션이 없을 때, Baron SSO 로그인 화면이 뜨기까지의 순서는 다음과 같다.
1. 사용자 브라우저 -> RP 업무시스템
사용자가 RP의 보호 페이지에 접속한다.
2. RP 업무시스템
RP가 자기 시스템의 로그인 세션을 확인한다.
세션이 없으면 Baron SSO로 보내야 한다고 판단한다.
3. RP 업무시스템 -> 사용자 브라우저
RP가 브라우저에게 Baron SSO OIDC 인증 URL로 이동하라고 응답한다.
예: /oidc/oauth2/auth?client_id=...&redirect_uri=...&scope=...&state=...
4. 사용자 브라우저 -> L7 -> Baron Gateway Nginx
브라우저가 Baron SSO의 /oidc/oauth2/auth 주소로 이동한다.
5. Baron Gateway Nginx -> Oathkeeper -> Hydra
Nginx는 /oidc/* 경로이므로 Oathkeeper로 보낸다.
Oathkeeper는 이를 Hydra Public endpoint로 전달한다.
6. Hydra
client_id, redirect_uri, scope, state, nonce, code_challenge 등을 확인한다.
즉, RP의 OIDC 로그인 요청이 정상인지 접수/검증한다.
7. Hydra
사용자의 Baron SSO 로그인 세션이 없으면 login_challenge를 만든다.
8. Hydra -> 사용자 브라우저
Hydra가 브라우저에게 UserFront 로그인 화면으로 이동하라고 리다이렉트한다.
예: /login?login_challenge=...
9. 사용자 브라우저 -> L7 -> Baron Gateway Nginx -> UserFront
브라우저가 실제 로그인 화면 URL을 다시 요청한다.
이 요청은 / 또는 /login 화면 요청이므로 Oathkeeper를 거치지 않고 UserFront로 간다.
10. UserFront -> 사용자 브라우저
비밀번호 / 로그인 링크 / QR 코드 탭이 있는 Baron SSO 로그인 화면이 표시된다.
핵심은 "두 번의 브라우저 이동"이 있다는 점이다.
첫 번째 이동은 RP가 Baron SSO의 OIDC endpoint로 보내는 이동이다.
사용자 브라우저 -> L7 -> Nginx -> Oathkeeper -> Hydra
두 번째 이동은 Hydra가 UserFront 로그인 화면으로 보내는 이동이다.
사용자 브라우저 -> L7 -> Nginx -> UserFront
따라서 "로그인 화면 요청은 Oathkeeper를 거치지 않는다"는 말은 두 번째 이동에 대한 설명이다. 반대로 "RP의 OIDC 인증 요청은 Oathkeeper를 거친다"는 말은 첫 번째 이동에 대한 설명이다.
3.2 Hydra가 먼저 나오는 이유
처음 흐름을 보면 Hydra가 Kratos보다 먼저 등장하기 때문에 혼동될 수 있다. 직관적으로는 "사용자 인증은 Kratos가 담당하니 Kratos가 먼저 나와야 하는 것 아닌가?"라고 생각할 수 있다.
그러나 Baron SSO의 RP 로그인 흐름에서는 사용자가 처음부터 Baron SSO 로그인 페이지에 직접 온 것이 아니다. 사용자는 먼저 RP 업무시스템에 접속하려고 한다. RP는 자기 세션이 없으면 Baron SSO의 OIDC endpoint로 브라우저를 보낸다.
이 시점에 Baron SSO가 먼저 판단해야 하는 것은 사용자의 비밀번호가 맞는지가 아니다. 먼저 확인해야 하는 것은 "어느 RP 시스템이 어떤 OIDC 로그인 요청을 보냈는가"이다.
역할을 나누면 다음과 같다.
| 질문 | 담당 |
|---|---|
| 어느 RP가 로그인을 요청했는가? | Hydra |
이 RP의 client_id가 정상인가? |
Hydra |
redirect_uri가 등록된 주소인가? |
Hydra |
요청한 scope, state, nonce, code_challenge가 정상인가? |
Hydra |
| 사용자가 실제 누구인가? | Kratos |
| 비밀번호, 코드, 세션이 유효한가? | Kratos |
| 인증된 사용자를 RP 로그인 요청에 연결하는가? | Baron Backend -> Hydra |
따라서 Hydra가 먼저 하는 일은 "사용자 인증"이 아니라 "RP 로그인 요청 접수"이다. 이때 생성되는 login_challenge도 사용자 식별자가 아니라 RP 로그인 요청 식별자다.
쉽게 표현하면 다음과 같다.
Hydra = RP 로그인 신청 접수처
Kratos = 사용자 본인확인 창구
Baron Backend = 두 시스템을 연결해서 완료 처리하는 중계자
전체 흐름은 다음처럼 이해하면 된다.
1. RP 로그인 요청 접수
담당: Hydra
2. 사용자 본인 인증
담당: Kratos
3. 인증된 사용자를 RP 로그인 요청에 연결
담당: Baron Backend -> Hydra
4. RP로 최종 복귀
담당: Hydra
즉 login_challenge는 "이 사용자가 누구인가"를 나타내는 값이 아니다. "이 RP 로그인 요청 건이 무엇인가"를 나타내는 임시 접수번호다. 사용자가 로그인 화면에서 비밀번호, 로그인 링크, QR 코드 중 하나로 인증을 마치면 Backend가 이 접수번호를 들고 Hydra에 AcceptLoginRequest를 호출하여 "이 접수 건은 이 사용자로 로그인 완료"라고 알려준다.
flowchart LR
User[사용자 브라우저]
RP[RP/업무시스템]
L7[Traefik 또는 외부 L7]
GW[Baron Gateway Nginx]
UF[UserFront 로그인 화면]
BE[Baron Backend]
OK[Oathkeeper]
KR[Ory Kratos]
HY[Ory Hydra]
Redis[Redis]
User -->|업무시스템 접속| RP
RP -->|OIDC 인증 요청 /oidc/oauth2/auth| L7
User -->|Baron SSO 화면/API 요청| L7
L7 --> GW
GW -->|/| UF
GW -->|/api/v1/*| BE
GW -->|/auth/*| OK
GW -->|/oidc/*| OK
OK --> KR
OK --> HY
BE --> Redis
BE --> KR
BE --> HY
운영 관점에서는 다음 경로를 구분해서 설명하면 이해하기 쉽다.
| 구분 | 경로 | 의미 |
|---|---|---|
| 화면 진입 | 사용자 -> L7 -> Nginx -> UserFront |
로그인 페이지를 보여주는 경로 |
| Baron API | UserFront -> L7 -> Nginx -> Backend |
로그인 버튼 클릭, 링크 발송, QR 생성, 폴링 요청 |
| Ory Public | 브라우저 또는 Nginx -> Oathkeeper -> Kratos/Hydra |
Ory public API 또는 OIDC public endpoint |
| 내부 백채널 | Backend -> Kratos/Hydra/Redis |
세션 발급, OIDC accept, 대기 상태 저장 |
4. 비밀번호 로그인
4.0 로그인 화면이 뜨기 전 순서
RP 화면에서 사용자가 로그인을 시도하면, 바로 비밀번호 검증이나 토큰 검증부터 하는 것이 아니다. 먼저 OIDC 인증 요청을 Baron SSO의 Hydra endpoint로 보낸다.
이때 Hydra가 확인하는 것은 주로 다음과 같다.
client_id: 어떤 RP 시스템이 요청했는지redirect_uri: 로그인 후 돌아갈 주소가 등록된 주소인지scope: RP가 요청한 권한 범위state,nonce,code_challenge: OIDC/PKCE 흐름을 안전하게 이어가기 위한 값- 기존 Baron SSO 로그인 세션 존재 여부
즉, 처음 /oidc/oauth2/auth로 들어온 요청은 "토큰 확인"이라기보다 "OIDC 로그인 요청 접수 및 검증"에 가깝다. 사용자가 아직 Baron SSO에 로그인되어 있지 않으면 Hydra는 login_challenge를 만들고, 사용자를 Baron SSO 로그인 화면(UserFront)으로 보낸다.
흐름을 로그인 화면 표시 전까지만 보면 다음과 같다.
sequenceDiagram
autonumber
actor User as 사용자
participant RP as RP/업무시스템
participant L7 as L7/Traefik
participant GW as Baron Gateway Nginx
participant OK as Oathkeeper
participant HY as Ory Hydra
participant UF as UserFront 로그인 화면
User->>RP: 업무시스템 접속 또는 보호 페이지 접근
RP->>RP: RP 자체 세션 확인
RP-->>User: RP 세션 없음, /oidc/oauth2/auth로 브라우저 리다이렉트
User->>L7: GET /oidc/oauth2/auth?client_id=...&redirect_uri=...
L7->>GW: 요청 전달
GW->>OK: /oidc/* 경로 전달
OK->>HY: strip_path 후 Hydra /oauth2/auth 전달
HY->>HY: client_id, redirect_uri, scope, state, PKCE 검증
alt 기존 Baron SSO 로그인 세션 없음
HY-->>User: login_challenge 포함 UserFront 로그인 화면으로 리다이렉트
User->>L7: UserFront 로그인 화면 요청
L7->>GW: 화면 요청 전달
GW->>UF: / 경로 UserFront로 전달
UF-->>User: 비밀번호/로그인 링크/QR 코드 화면 표시
else 기존 Baron SSO 로그인 세션 있음
HY-->>User: 로그인 화면 생략 또는 consent/redirect 단계로 진행
end
정리하면 다음 순서다.
- 사용자가 RP에 접속한다.
- RP가
client_id등을 담아 Baron SSO의/oidc/oauth2/auth로 브라우저를 보낸다. - 요청은 L7, Baron Gateway Nginx, Oathkeeper를 거쳐 Hydra로 간다.
- Hydra는 RP 요청이 정상인지 확인하고, 기존 SSO 세션이 있는지 본다.
- 로그인 세션이 없으면
login_challenge를 발급하고 UserFront 로그인 화면으로 보낸다. - 그 다음에야 사용자가 비밀번호/로그인 링크/QR 코드 중 하나를 선택한다.
4.1 사용자가 보는 절차
- 사용자가
비밀번호탭을 선택한다. 이메일 또는 휴대폰 번호를 입력한다.비밀번호를 입력한다.- 로그인 버튼을 누른다.
- 인증 성공 시 Baron SSO 세션이 만들어지고, OIDC 로그인 중이면 원래 RP 시스템으로 돌아간다.
- 실패 시 비밀번호 오류, 미등록 사용자, 비활성 사용자 등의 메시지가 표시된다.
4.2 주요 상황
| 상황 | 처리 |
|---|---|
| 사용자가 이메일 입력 | 입력값을 그대로 loginId로 사용 |
| 사용자가 휴대폰 번호 입력 | 010-1234-5678 같은 값을 정리하여 +821012345678 형태로 정규화 |
| 일반 UserFront 로그인 | sessionJwt를 받아 UserFront 세션으로 사용 |
| RP OIDC 로그인 중 | login_challenge를 함께 보내고, 성공 후 Hydra AcceptLoginRequest를 거쳐 redirectTo로 이동 |
4.3 데이터 예시
요청 예시:
{
"loginId": "user@example.com",
"password": "********",
"login_challenge": "optional-hydra-login-challenge"
}
응답 예시:
{
"status": "ok",
"provider": "kratos",
"sessionJwt": "eyJ...",
"token": "eyJ...",
"redirectTo": "https://sso.example.com/oidc/oauth2/auth?..."
}
redirectTo는 OIDC 로그인 흐름일 때 중요하다. RP 시스템에서 Baron SSO로 보낸 로그인 요청을 Hydra가 이어받고, Backend가 로그인 성공을 Hydra에 알려주면, Hydra가 최종 이동할 URL을 돌려준다.
4.4 흐름도
아래 흐름도는 "로그인 화면 표시 전 OIDC 요청 접수"와 "비밀번호 입력 후 세션 발급"을 한 번에 이어서 보여준다.
주의할 점은 4번의 /oidc/* -> Hydra 단계가 비밀번호나 access token을 검사하는 단계가 아니라는 것이다. 이 단계는 RP의 OIDC 인증 요청을 Hydra가 접수하고, client_id와 redirect_uri 등을 확인한 뒤 login_challenge를 만들어 UserFront 로그인 화면으로 보내는 단계다.
sequenceDiagram
autonumber
actor User as 사용자
participant RP as RP/업무시스템
participant L7 as L7/Traefik
participant GW as Baron Gateway Nginx
participant OK as Oathkeeper
participant UF as UserFront
participant BE as Baron Backend
participant KR as Ory Kratos
participant HY as Ory Hydra
User->>RP: 업무시스템 접속
RP-->>User: /oidc/oauth2/auth?client_id=... 로 리다이렉트
User->>L7: GET /oidc/oauth2/auth?client_id=...
L7->>GW: OIDC 요청 전달
GW->>OK: /oidc/* 경로 전달
OK->>HY: Hydra /oauth2/auth 전달
HY->>HY: RP 요청 검증 및 login_challenge 생성
HY-->>User: UserFront 로그인 화면으로 리다이렉트
User->>UF: 로그인 화면 표시
User->>UF: 이메일/휴대폰 + 비밀번호 입력
UF->>L7: POST /api/v1/auth/password/login
L7->>GW: API 요청 전달
GW->>BE: /api/v1/auth/password/login
BE->>KR: Kratos password 인증
KR-->>BE: session_token, cookie
alt login_challenge 있음
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo
BE-->>UF: sessionJwt + redirectTo
UF->>RP: OIDC callback 이동
else 일반 로그인
BE-->>UF: sessionJwt
end
5. 로그인 링크
5.1 사용자가 보는 절차
- 사용자가
로그인 링크탭을 선택한다. - 이메일 또는 휴대폰 번호를 입력한다.
로그인 링크 전송을 누른다.- 현재 브라우저는 로그인 대기 상태가 된다.
- 사용자는 이메일/SMS로 받은 링크를 클릭하거나, 전달받은 코드를 입력한다.
- 승인 또는 코드 검증이 성공하면 원래 대기 중이던 브라우저가 로그인 완료된다.
5.2 주요 상황
| 상황 | 처리 |
|---|---|
| 이메일 입력 | 이메일로 로그인 링크 또는 코드 전달 |
| 휴대폰 번호 입력 | SMS로 로그인 링크 또는 코드 전달 |
| 요청 브라우저 | pendingRef를 들고 poll을 반복 호출 |
| 승인 브라우저/기기 | 링크 클릭 또는 코드 검증으로 pendingRef 승인 |
| verifyOnly 승인 | 승인 기기에는 세션을 만들지 않고, 원래 요청 브라우저만 로그인 완료 |
로그인 링크 방식의 핵심은 pendingRef이다. pendingRef는 "로그인을 기다리는 요청"을 식별하는 임시 키다. 현재 브라우저는 이 키로 계속 상태를 확인하고, 링크를 클릭한 쪽이 해당 키를 승인하면 현재 브라우저가 세션을 받는다.
사용자 존재 여부 확인은 Backend 핸들러에서 시작하지만, Backend가 자체 DB만 보고 단독 판단하는 구조로 이해하면 안 된다. Backend는 IdpProvider.UserExists(loginId)를 호출하고, 현재 Ory provider 기준으로는 Kratos Admin API를 통해 해당 loginId의 identity가 있는지 확인한다.
즉 그림의 "사용자 존재 여부 확인"은 다음 의미다.
Baron Backend
-> IdpProvider.UserExists(loginId)
-> Ory/Kratos provider
-> Kratos Admin API 또는 IDP 조회
-> identity 존재 여부 반환
Backend가 이 확인을 먼저 하는 이유는 존재하지 않는 사용자에게 로그인 링크나 SMS를 보내지 않기 위해서다. 또한 휴대폰 번호 입력 시 010-1234-5678 같은 값을 +821012345678 형태로 정규화한 뒤 조회한다.
5.3 데이터 예시
초기 요청:
{
"loginId": "user@example.com",
"uri": "https://sso.example.com",
"codeOnly": false
}
초기 응답:
{
"linkId": "Sent",
"pendingRef": "AbC123",
"mode": "code",
"provider": "kratos",
"expiresIn": 300,
"interval": 2,
"resendAfter": 30
}
Redis 대기 상태 예시:
{
"key": "enchanted_session:AbC123",
"value": {
"status": "pending",
"loginId": "user@example.com"
},
"ttl": "5m"
}
폴링 중 대기 응답:
{
"error": "authorization_pending",
"code": "authorization_pending",
"interval": 2
}
승인 후 성공 응답:
{
"status": "ok",
"sessionJwt": "eyJ..."
}
5.4 흐름도
sequenceDiagram
autonumber
actor User as 사용자
participant UF as 요청 브라우저(UserFront)
participant L7 as L7/Traefik
participant GW as Baron Gateway Nginx
participant BE as Baron Backend
participant Redis as Redis
participant KR as Ory Kratos/Courier
participant Channel as Email/SMS
participant Approver as 링크 승인 기기
User->>UF: 로그인 링크 탭에서 이메일/휴대폰 입력
UF->>L7: POST /api/v1/auth/enchanted-link/init
L7->>GW: API 요청 전달
GW->>BE: init 요청
BE->>BE: 사용자 존재 여부 확인, 전화번호 정규화
BE->>KR: 링크/코드 로그인 시작
BE->>Redis: pendingRef 상태 저장(status=pending)
KR-->>Channel: 로그인 링크 또는 코드 발송
BE-->>UF: pendingRef, expiresIn, interval
loop 요청 브라우저 대기
UF->>BE: POST /api/v1/auth/enchanted-link/poll(pendingRef)
BE->>Redis: pendingRef 상태 조회
BE-->>UF: authorization_pending 또는 slow_down
end
User->>Approver: 수신한 링크 클릭 또는 코드 입력
Approver->>BE: POST /api/v1/auth/magic-link/verify 또는 /login/code/verify
BE->>Redis: pendingRef 승인 처리(status=approved/success)
UF->>BE: poll 재시도
BE-->>UF: sessionJwt
5.5 보안상 설명 포인트
로그인 링크는 비밀번호를 입력하지 않아 편리하지만, 링크를 받은 사람이 곧 승인자가 된다. 따라서 운영 설명 시 다음 통제가 필요하다.
- 링크 만료 시간은 짧게 유지한다.
- 재전송 제한과 폴링 속도 제한을 둔다.
- 승인 화면에는 요청 기기, IP, 브라우저, 요청 시각을 표시한다.
- "내가 요청한 로그인이 아님" 버튼을 제공한다.
- 요청자와 승인자를 감사 로그에 분리 기록한다.
6. QR 코드 로그인
6.1 사용자가 보는 절차
- 사용자가
QR 코드탭을 선택한다. - 로그인 화면에 QR 코드가 표시된다.
- 사용자는 이미 로그인되어 있는 기기에서 QR을 스캔한다.
- 승인 기기에서 로그인 요청 정보를 확인하고 승인한다.
- 원래 QR을 띄운 브라우저가 로그인 완료된다.
6.2 주요 상황
| 상황 | 처리 |
|---|---|
| PC에서 새 로그인 필요 | PC 화면에 QR 표시 |
| 사용자가 모바일/다른 브라우저에 이미 로그인되어 있음 | 해당 기기로 QR 스캔 후 승인 |
| 승인 기기에 세션 없음 | 승인 불가, 먼저 승인 기기에서 로그인 필요 |
| QR 만료 | expired_token 처리 후 새 QR 발급 필요 |
| 폴링 과다 | slow_down으로 폴링 간격 증가 |
QR 방식은 "이미 로그인된 기기"를 신뢰 근거로 쓴다. 즉 QR을 스캔하는 기기가 Baron SSO 세션을 가지고 있어야 하며, 그 세션의 사용자가 QR을 띄운 브라우저에 대한 로그인을 승인하는 구조다.
6.3 데이터 예시
QR 초기화 응답:
{
"qrCode": "https://sso.example.com/ql/QR_REF_64_CHARS",
"pendingRef": "PENDING_REF",
"expiresIn": 300,
"interval": 2
}
Redis 저장 예시:
[
{
"key": "enchanted_session:PENDING_REF",
"value": {
"status": "pending"
},
"ttl": "5m"
},
{
"key": "qr_ref:QR_REF_64_CHARS",
"value": "PENDING_REF",
"ttl": "5m"
}
]
승인 요청 예시:
{
"pendingRef": "PENDING_REF"
}
승인 요청은 쿠키 세션 또는 토큰 세션을 통해 승인 사용자를 확인한다. 승인 후에는 QR을 띄운 브라우저 쪽 pendingRef가 성공 상태로 바뀐다.
6.4 흐름도
sequenceDiagram
autonumber
actor User as 사용자
participant PC as 요청 PC(UserFront)
participant L7 as L7/Traefik
participant GW as Baron Gateway Nginx
participant BE as Baron Backend
participant Redis as Redis
participant Mobile as 이미 로그인된 승인 기기
participant KR as Ory Kratos
User->>PC: QR 코드 탭 선택
PC->>L7: POST /api/v1/auth/qr/init
L7->>GW: API 요청 전달
GW->>BE: QR init
BE->>Redis: pendingRef/status=pending 저장
BE->>Redis: qrRef -> pendingRef 매핑 저장
BE-->>PC: qrCode URL, pendingRef, expiresIn
PC->>PC: QR 코드 표시
loop 요청 PC 대기
PC->>BE: POST /api/v1/auth/qr/poll(pendingRef)
BE->>Redis: pendingRef 상태 조회
BE-->>PC: authorization_pending 또는 slow_down
end
User->>Mobile: QR 스캔
Mobile->>BE: POST /api/v1/auth/qr/approve(pendingRef 또는 qrRef)
BE->>KR: 승인 기기 세션 확인(whoami/session)
BE->>KR: QR 대상 사용자의 코드 로그인/세션 발급 처리
BE->>Redis: pendingRef/status=success, jwt 저장
BE-->>Mobile: QR Login Approved
PC->>BE: poll 재시도
BE-->>PC: sessionJwt
6.5 QR 방식의 핵심 통제
QR 로그인은 편리하지만, 공격자가 자기 PC에 QR을 띄우고 사용자에게 스캔을 유도할 수 있다. 따라서 승인 화면에는 반드시 다음 정보가 필요하다.
| 승인 화면 표시 정보 | 이유 |
|---|---|
| 요청 기기 종류 | 내 PC인지 확인 |
| 요청 브라우저 | 평소 사용하는 브라우저인지 확인 |
| 요청 IP/대략 위치 | 낯선 위치 요청인지 확인 |
| 요청 시각 | 방금 내가 요청한 것인지 확인 |
| RP 시스템 이름 | 어떤 업무시스템 로그인을 승인하는지 확인 |
또한 승인 버튼과 별도로 내가 요청한 로그인이 아님 버튼을 제공해야 한다. 이 버튼은 해당 pendingRef를 차단하고, 필요 시 세션/refresh grant revoke로 이어져야 한다.
7. 세 방식 비교
| 비교 항목 | 비밀번호 | 로그인 링크 | QR 코드 |
|---|---|---|---|
| 현재 화면에서 인증 완료 가능 | 가능 | 불가능, 외부 링크/코드 필요 | 불가능, 승인 기기 필요 |
| 비밀번호 필요 | 필요 | 불필요 | 불필요 |
| 기존 로그인 기기 필요 | 불필요 | 필수는 아님 | 사실상 필요 |
| 이메일/SMS 채널 필요 | 불필요 | 필요 | 불필요 |
| 교차 기기 승인 | 아님 | 가능 | 핵심 기능 |
| 주요 임시 키 | 없음 | pendingRef, magic token, login code |
pendingRef, qrRef |
| 주요 위험 | 비밀번호 탈취, 무차별 대입 | 링크 탈취, 오발송, 피싱 승인 | QR 피싱, 잘못된 승인 |
| 운영 통제 | 비밀번호 정책, 실패 횟수 제한 | TTL, 재전송 제한, 승인 정보 표시 | TTL, 승인 정보 표시, 기존 세션 검증 |
8. 팀 설명용 쉬운 표현
비밀번호 로그인은 "본인이 지금 이 화면에서 비밀번호를 직접 증명하는 방식"이다.
로그인 링크는 "지금 화면은 대기표를 뽑고, 이메일이나 문자로 받은 링크를 눌러 그 대기표를 승인하는 방식"이다.
QR 코드는 "새 PC 화면에 임시 출입증을 띄우고, 이미 로그인된 내 기기로 그 출입증을 스캔해서 승인하는 방식"이다.
세 방식 모두 최종 목적은 같다. Baron Backend가 사용자를 확인하고, Kratos 세션을 얻은 뒤, RP 로그인이면 Hydra OIDC 흐름을 완료하여 사용자를 원래 업무시스템으로 돌려보낸다. 다만 "사용자 본인임을 확인하는 수단"이 각각 다르다.
9. 운영/보안 권고
- 비밀번호 방식은 기본 로그인 수단으로 유지하되, 실패 횟수 제한과 감사 로그를 반드시 유지한다.
- 로그인 링크 방식은 만료 시간, 재전송 제한, 링크 재사용 방지를 강하게 적용한다.
- QR 방식은 승인 화면에 요청 정보를 충분히 보여주고, 사용자가 명확히 승인하도록 한다.
- 링크/QR 방식 모두
요청자 기기와승인자 기기를 감사 로그에 분리 저장한다. 내가 요청한 로그인이 아님을 누르면 해당 pending 요청을 즉시 차단하고, 이미 발급된 세션 또는 refresh grant가 있으면 단계적으로 revoke한다.- 내부 사용자는 Naver Works 같은 조직 채널 알림과 연동할 수 있지만, 외부 사용자는 이메일/패스키/TOTP 등 별도 신뢰 채널을 마련해야 한다.