Keycloak 과 기업 인증 · 애플리케이션 연동(리다이렉트·PKCE) · 실습
authorization code + PKCE 흐름 완주하기
목표
authorization code + PKCE 흐름을 SDK 없이 처음부터 끝까지 수행하고, PKCE 와 state 가 각각 무엇을 막는지 실패 케이스로 확인한다.
왜 중요한가
OIDC 라이브러리를 쓰면 이 흐름은 함수 호출 두 번입니다. 그래서 무엇이 일어나는지 모르는 채로 쓰게 되고, 문제가 생기면 어디를 봐야 할지 모릅니다. 이 실습에서 각 단계를 손으로 하면 세 가지가 분명해집니다. 첫째, 인가 코드가 브라우저 URL 에 노출되지만 왜 그것만으로는 쓸모없는지. 둘째, code_verifier 를 모르면 왜 교환이 실패하는지 — 6번 스텝에서 직접 실패시켜 봅니다. 셋째, state 가 없으면 왜 공격자가 피해자를 자기 계정에 로그인시킬 수 있는지. 마지막 리프레시 회전까지 보면 실무에서 권장되는 조합(짧은 액세스 토큰 + 리프레시 회전)이 왜 블랙리스트 없이도 무효화 효과를 내는지 이해됩니다.
Keycloak 시작에 40~90초가 걸리므로 앞선 실습을 마치고 오면 대개 준비돼 있습니다.
단계
1. /root/pk/pkce.py 로 code_verifier(43~128자, URL 안전 문자)와 code_challenge 를 만들어 /root/pk/verifier.txt 와 /root/pk/challenge.txt 에 각각 한 줄씩 저장한다.
2. /root/pk/authz_url.txt 에 인가 요청 URL 을 한 줄 적는다. 대상은 사전 임포트된 렐름 labhub 의 공개 클라이언트 web-app 이고 리다이렉트 URI 는 http://127.0.0.1:8161/callback 이다. response_type=code, client_id, redirect_uri, scope=openid, state, code_challenge, code_challenge_method=S256 이 모두 있어야 한다.
3. 쿠키 항아리를 유지하며 그 URL 을 GET 해 로그인 폼의 action URL 을 /root/pk/action.txt 에 저장한다. http 로 시작해야 한다.
4. 사용자 dev1 / 비밀번호 Dev1!pass 를 POST 해 리다이렉트 응답의 Location 에서 인가 코드를 뽑아 /root/pk/code.txt 에 저장한다. 20자 이상이어야 한다.
5. 토큰 엔드포인트에 grant_type=authorization_code, 코드, redirect_uri, client_id, code_verifier 를 보내 토큰을 받는다. /root/pk/tokens.json 에 access_token 과 refresh_token 이 있어야 한다.
6. 새 인가 코드를 받아 틀린 code_verifier 로 교환을 시도한다. /root/pk/pkce_fail.json 의 error 가 invalid_grant 여야 한다.
7. /root/pk/state_check.py 는 인자 두 개(보낸 state, 받은 state)를 받아 같으면 종료 코드 0, 다르면 1 로 끝난다. 일치·불일치 두 경우의 결과를 /root/pk/state.out 에 match=ok mismatch=rejected 로 적는다.
8. 리프레시 토큰으로 갱신해 /root/pk/refresh.json 을 만든다. /root/pk/rotation.txt 에 rotated=<true|false> 를 적는다. 판단 근거는 새 리프레시 토큰이 이전과 다른지이다.
참고
- challenge 계산:
base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b'=') - 쿠키 유지:
curl -c /root/pk/cookies -b /root/pk/cookies ... - 리다이렉트를 따라가지 않아야 Location 헤더를 볼 수 있습니다(
-i와 함께-L없이). - 흔한 실수 1: 해시 결과를 16진 문자열로 만들어 Base64 하는 것 — 이진 다이제스트를 인코딩해야 합니다.
- 흔한 실수 2: 토큰 교환의
redirect_uri가 인가 요청 때와 달라invalid_grant가 나는 것.
단계 8개
- code_verifier 와 challenge 만들기
- 인가 요청 URL 조립하기
- 로그인 폼의 action 추출하기
- 자격 증명 POST 해서 코드 받기
- 코드를 토큰으로 교환하기
- 틀린 verifier 로 실패 확인하기
- state 불일치 검증 구현하기
- 리프레시와 회전 확인하기