LabHub
배우기 러닝패스 코스

Keycloak and Enterprise Identity

Completing the Authorization Code + PKCE Flow

LabHub 에서 이어서 보기

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

목표

authorization code + PKCE 흐름을 SDK 없이 처음부터 끝까지 수행하고, PKCE 와 state 가 각각 무엇을 막는지 실패 케이스로 확인한다.

왜 중요한가

OIDC 라이브러리를 쓰면 이 흐름은 함수 호출 두 번입니다. 그래서 무엇이 일어나는지 모르는 채로 쓰게 되고, 문제가 생기면 어디를 봐야 할지 모릅니다. 이 실습에서 각 단계를 손으로 하면 세 가지가 분명해집니다. 첫째, 인가 코드가 브라우저 URL 에 노출되지만 왜 그것만으로는 쓸모없는지. 둘째, code_verifier 를 모르면 왜 교환이 실패하는지 — 6번 스텝에서 직접 실패시켜 봅니다. 셋째, state 가 없으면 왜 공격자가 피해자를 자기 계정에 로그인시킬 수 있는지. 마지막 리프레시 회전까지 보면 실무에서 권장되는 조합(짧은 액세스 토큰 + 리프레시 회전)이 왜 블랙리스트 없이도 무효화 효과를 내는지 이해됩니다.

Keycloak 시작에 40~90초가 걸리므로 앞선 실습을 마치고 오면 대개 준비돼 있습니다.

단계

  1. /root/pk/pkce.pycode_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.jsonaccess_tokenrefresh_token 이 있어야 한다.
  6. 새 인가 코드를 받아 틀린 code_verifier 로 교환을 시도한다. /root/pk/pkce_fail.jsonerrorinvalid_grant 여야 한다.
  7. /root/pk/state_check.py 는 인자 두 개(보낸 state, 받은 state)를 받아 같으면 종료 코드 0, 다르면 1 로 끝난다. 일치·불일치 두 경우의 결과를 /root/pk/state.outmatch=ok mismatch=rejected 로 적는다.
  8. 리프레시 토큰으로 갱신해 /root/pk/refresh.json 을 만든다. /root/pk/rotation.txtrotated=<true|false> 를 적는다. 판단 근거는 새 리프레시 토큰이 이전과 다른지이다.

참고

code_verifier 와 challenge 만들기

/root/pk/pkce.pycode_verifier(43~128자, URL 안전 문자)와 code_challenge 를 만들어 /root/pk/verifier.txt/root/pk/challenge.txt 에 각각 한 줄씩 저장한다.

길이 제한과 문자 집합이 규격에 정해져 있습니다. 해시 결과를 그대로 Base64 하면 안 되고 URL 안전 인코딩에 패딩을 빼야 합니다.

인가 요청 URL 조립하기

/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 이 모두 있어야 한다.

필수 파라미터가 여섯 개입니다. 하나라도 빠지면 인증 서버가 오류를 줍니다.

로그인 폼의 action 추출하기

쿠키 항아리를 유지하며 그 URL 을 GET 해 로그인 폼의 action URL 을 /root/pk/action.txt 에 저장한다. http 로 시작해야 한다.

인가 엔드포인트를 GET 하면 HTML 이 옵니다. 쿠키를 유지해야 다음 단계가 됩니다.

자격 증명 POST 해서 코드 받기

사용자 dev1 / 비밀번호 Dev1!pass 를 POST 해 리다이렉트 응답의 Location 에서 인가 코드를 뽑아 /root/pk/code.txt 에 저장한다. 20자 이상이어야 한다.

폼 필드 이름을 HTML 에서 확인하세요. 성공하면 리다이렉트 응답의 위치 헤더에 코드가 들어 있습니다.

코드를 토큰으로 교환하기

토큰 엔드포인트에 grant_type=authorization_code, 코드, redirect_uri, client_id, code_verifier 를 보내 토큰을 받는다. /root/pk/tokens.jsonaccess_tokenrefresh_token 이 있어야 한다.

인가 코드와 원본 verifier 를 함께 보냅니다. 리다이렉트 URI 도 처음과 정확히 같아야 합니다.

틀린 verifier 로 실패 확인하기

새 인가 코드를 받아 틀린 code_verifier 로 교환을 시도한다. /root/pk/pkce_fail.jsonerrorinvalid_grant 여야 한다.

이것이 PKCE 가 실제로 작동한다는 증거입니다. 오류 코드를 기록하세요.

state 불일치 검증 구현하기

/root/pk/state_check.py 는 인자 두 개(보낸 state, 받은 state)를 받아 같으면 종료 코드 0, 다르면 1 로 끝난다. 일치·불일치 두 경우의 결과를 /root/pk/state.outmatch=ok mismatch=rejected 로 적는다.

인가 요청에 보낸 값과 콜백으로 돌아온 값을 비교합니다. 다르면 거부해야 합니다.

리프레시와 회전 확인하기

리프레시 토큰으로 갱신해 /root/pk/refresh.json 을 만든다. /root/pk/rotation.txtrotated=<true|false> 를 적는다. 판단 근거는 새 리프레시 토큰이 이전과 다른지이다.

갱신하면 새 리프레시 토큰이 옵니다. 옛것과 같은지 다른지가 회전 여부입니다.