LabHub
배우기 러닝패스 코스

Keycloak and Enterprise Identity

Four Flows and Where Each Belongs

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

공개 클라이언트에는 authorization code + PKCE, 서버 간 통신에는 client credentials. 나머지는 대부분 쓰지 말아야 할 이유가 있는 흐름이다.

Flow map: 받은 토큰을 어디에 보관하느냐 · BFF(Backend For Frontend) · 어느 쪽을 고르든 XSS 가 있으면 대부분 무너진다는 점 · 액세스 토큰은 취소되지 않으므로

왜 이게 필요했나

"구글 로그인 버튼 하나에 왜 이렇게 복잡한 프로토콜이 필요한가"라는 질문이 자연스럽습니다. 답은 이것입니다. 사용자의 비밀번호를 제3자 앱에 주지 않으면서, 그 앱이 사용자를 대신해 제한된 자원에 접근하게 하려는 것입니다.

이 목표를 위해 여러 흐름이 만들어졌고, 시간이 지나며 몇 개는 폐기됐습니다.

어떻게 동작하나

Authorization Code 가 기본이자 표준입니다. 사용자가 인증 서버로 리다이렉트되어 로그인하고, 인가 코드를 받아 앱으로 돌아오고, 앱의 백엔드가 그 코드를 토큰으로 교환합니다. 코드가 브라우저 URL 에 노출되지만 1회용이고 단독으로는 쓸 수 없다는 점이 핵심입니다. 교환에는 클라이언트 시크릿이 필요합니다.

문제는 SPA 와 모바일 앱입니다. 이들은 시크릿을 안전하게 보관할 수 없습니다. 소스에 넣으면 누구나 볼 수 있습니다. 그래서 예전에는 Implicit 흐름 — 코드 없이 토큰을 바로 URL 프래그먼트로 받는 방식 — 을 썼습니다. 토큰이 브라우저 히스토리와 리퍼러에 남는 심각한 문제가 있어 지금은 권장되지 않습니다.

대체가 PKCE(Proof Key for Code Exchange)입니다. 클라이언트가 매 요청마다 무작위 문자열 code_verifier 를 만들고, 그 SHA256 해시를 Base64URL 로 인코딩한 code_challenge 를 인가 요청에 실어 보냅니다. 나중에 코드를 토큰으로 교환할 때 원본 code_verifier 를 제출합니다. 인증 서버는 해시를 다시 계산해 맞춰 봅니다. 공격자가 인가 코드를 가로채도 code_verifier 가 없으면 토큰을 받지 못합니다. 시크릿 없이도 안전해지는 것입니다.

Client Credentials 는 사용자가 개입하지 않는 서버 간 통신용입니다. 배치 작업, 서비스 계정, 내부 API 호출. 사용자 컨텍스트가 없으므로 sub 는 서비스 계정입니다.

Resource Owner Password 는 앱이 사용자의 아이디와 비밀번호를 직접 받아 토큰으로 바꾸는 흐름입니다. OAuth 가 없애려던 바로 그 패턴이라 사실상 폐기됐습니다. 레거시 마이그레이션 외에는 쓰지 않습니다.

현장에서 만나는 모습

토큰 종류별 저장 위치와 수명을 정리하면 이렇습니다.

토큰 용도 수명 저장 위치
Authorization Code 토큰 교환용 1회성 수 초 URL 파라미터(즉시 소멸)
ID Token 사용자 신원 확인 5~60분 클라이언트
Access Token API 접근 권한 5~60분 메모리 또는 BFF
Refresh Token 액세스 토큰 갱신 길다(취소 가능) httpOnly 쿠키 또는 서버

리프레시 토큰을 localStorage 에 두면 안 되는 이유는 명확합니다. XSS 로 자바스크립트가 읽을 수 있고, 리프레시 토큰은 장기 유효라 탈취되면 지속적 접근을 허용합니다.

state 파라미터도 빠뜨리면 안 됩니다. 이것이 없으면 공격자가 자기 계정으로 발급받은 인가 코드를 피해자의 콜백으로 보내 피해자를 공격자 계정에 로그인시킬 수 있습니다.

브라우저 앱에서 토큰을 어디에 두나

흐름을 올바로 골라도 받은 토큰을 어디에 보관하느냐에서 다시 갈린다. 브라우저에서 고를 수 있는 자리는 셋뿐이고 각각 무엇에 약한지가 다르다.

자리 약한 지점 비고
localStorage XSS 로 그대로 읽힌다 새로고침에도 남아 편하지만 가장 위험하다
자바스크립트 메모리 XSS 에는 여전히 노출, 새로고침하면 사라짐 액세스 토큰을 짧게 들고 있기에는 무난
httpOnly 쿠키 자바스크립트가 못 읽지만 CSRF 를 따로 막아야 한다 SameSite 와 검증이 함께 필요

여기서 나온 실무의 답이 BFF(Backend For Frontend) 다. 토큰을 브라우저에 아예 주지 않고 서버가 들고 있으면서, 브라우저에는 세션 쿠키만 준다. 브라우저의 요청을 서버가 받아 토큰을 붙여 뒤로 전달하는 구조다. 앞의 세 자리가 가진 문제를 한꺼번에 없애는 대신 서버 한 층이 늘어난다.

어느 쪽을 고르든 XSS 가 있으면 대부분 무너진다는 점은 분명히 해 두어야 한다. 스크립트가 실행되면 토큰을 직접 읽지 못하더라도 그 페이지에서 요청을 대신 보낼 수 있기 때문이다. 그래서 토큰 보관 위치를 고민하는 것과 같은 무게로 콘텐츠 보안 정책과 입력 처리에 신경 써야 한다.

로그아웃도 생각보다 간단하지 않다. 앞 모듈에서 본 대로 액세스 토큰은 취소되지 않으므로, 로그아웃은 리프레시 토큰을 무효화하고 클라이언트가 들고 있는 것을 버리는 일에 가깝다. 여러 애플리케이션이 같은 로그인 세션을 공유한다면 한 곳에서 로그아웃했을 때 나머지도 정리되어야 하는데, 그것을 위한 별도의 규약이 있고 앱마다 그것을 구현해 두어야 한다. "로그아웃 버튼을 눌렀으니 끝" 이 아니라, 어디까지 정리되는지를 설계에서 정해 두는 일입니다.

다음 확인에서 볼 것

이 모듈은 개념만 다룹니다. 다음 모듈부터 실제 Keycloak 에서 토큰을 받아 뜯어보고, 그다음 PKCE 흐름을 스크립트로 완주합니다.