LabHub

Keycloak 과 기업 인증 · OAuth2 그랜트 타입 지형도 · 이론

네 가지 흐름과 그 자리

LabHub 에서 이어서 보기

한 줄 요약

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

왜 이게 필요했나

"구글 로그인 버튼 하나에 왜 이렇게 복잡한 프로토콜이 필요한가"라는 질문이 자연스럽습니다. 답은 이것입니다. 사용자의 비밀번호를 제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 파라미터도 빠뜨리면 안 됩니다. 이것이 없으면 공격자가 자기 계정으로 발급받은 인가 코드를 피해자의 콜백으로 보내 피해자를 공격자 계정에 로그인시킬 수 있습니다.

다음 확인에서 볼 것

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