OIDC認可コードフローを手で再現する
한국어 원문으로 표시합니다.
목표
실습용 OIDC 제공자를 상대로 인가 코드 흐름을 직접 손으로 수행하고, ID 토큰을 디코드·서명 검증하고, 검증 체크리스트를 작성할 수 있게 됩니다.
왜 중요한가
OIDC 를 라이브러리로만 써 본 사람은 장애가 났을 때 어디를 봐야 할지 모릅니다.
redirect_uri 불일치, state 미검증, nonce 누락, 만료된 코드 재사용 —
전부 흐름을 눈으로 본 적이 있어야 감이 옵니다.
특히 ID 토큰은 base64 라 누구나 디코딩할 수 있다는 사실을 손으로 확인하는 것이
중요합니다. 페이로드를 고쳐도 서명 검증을 안 하면 그대로 통과한다는 것을
직접 재현해 보면, 왜 alg 를 고정해야 하는지가 몸으로 남습니다.
단계
- 실습용 IdP 를 기동합니다.
python3 /opt/lab/fixtures/auth/oidc/idp.py 9000(백그라운드) discovery 문서를 받아/root/oidc/discovery.json에 저장합니다. (http://127.0.0.1:9000/.well-known/openid-configuration)issuer,authorization_endpoint,token_endpoint,jwks_uri가 있어야 합니다. /root/oidc/auth-url.txt에 authorization URL 한 줄을 적습니다. 필수 파라미터:response_type=code,client_id=labhub-web,redirect_uri=http://127.0.0.1:9100/callback,scope=openid profile email,state=<16자 이상>,nonce=<16자 이상>.- 그 URL 을 리다이렉트를 따라가지 않고 호출해 응답의
Location헤더를/root/oidc/callback.txt에 저장하고, 거기서code값만 뽑아/root/oidc/code.txt에 저장합니다. Location 의state는 2단계에서 보낸 값과 같아야 합니다. - 토큰 엔드포인트로 코드를 교환해 응답 JSON 전체를
/root/oidc/token.json에 저장합니다.client_id=labhub-web,client_secret=labhub-secret을 함께 보냅니다.access_token,id_token,token_type이 있어야 하고token_type은Bearer입니다. id_token의 페이로드를 디코드해/root/oidc/claims.json에 저장합니다.iss,aud,sub,exp,nonce가 있어야 하고,aud는labhub-web,nonce는 2단계에서 보낸 값과 같아야 합니다./root/oidc/verify.sh를 만듭니다. 인자 두 개(ID토큰 공개키PEM)를 받아 서명이 유효하면 종료코드 0, 아니면 0 이 아닌 값으로 끝냅니다. 공개키는/opt/lab/fixtures/auth/oidc/idp-public.pem에 있습니다.access_token으로 userinfo 엔드포인트를 호출해/root/oidc/userinfo.json에 저장합니다.sub값이 5단계claims.json의sub와 같아야 합니다./root/oidc/checklist.csv를 만듭니다. 첫 줄은item,risk.서명,iss,aud,exp,nonce,alg여섯 항목이item에 있어야 하고,risk에는 그 항목을 검증하지 않으면 가능한 공격/사고를 10자 이상으로 적습니다.
참고
- 리다이렉트 안 따라가기:
curl -s -D - -o /dev/null "<URL>"후Location:줄 확인 - base64url 디코드:
-를+로,_를/로 바꾸고 패딩(=)을 채운 뒤base64 -d - 서명 검증: 서명 대상은
<헤더>.<페이로드>문자열, 알고리즘은 RS256(SHA-256)openssl dgst -sha256 -verify <공개키> -signature <서명파일> <데이터파일> - 흔한 실수 1: 인가 코드를 두 번 쓰는 것. 일회용이라 두 번째는 실패합니다.
- 흔한 실수 2: base64url 을 그냥
base64 -d로 넘겨 오류가 나는 것. - 흔한 실수 3:
redirect_uri를 토큰 교환 때 authorization 때와 다르게 보내는 것. 두 값이 정확히 같아야 합니다.
IdP 기동과 discovery 문서
실습용 IdP 를 기동합니다.
python3 /opt/lab/fixtures/auth/oidc/idp.py 9000 (백그라운드)
discovery 문서를 받아 /root/oidc/discovery.json 에 저장합니다.
(http://127.0.0.1:9000/.well-known/openid-configuration)
issuer, authorization_endpoint, token_endpoint, jwks_uri 가 있어야 합니다.
OIDC 제공자는 표준 경로에 설정 문서를 둡니다. 그 문서 하나로 엔드포인트 주소를 전부 알 수 있습니다. jq 로 필요한 값만 뽑아 보세요.
authorization URL 조립
/root/oidc/auth-url.txt 에 authorization URL 한 줄을 적습니다.
필수 파라미터: response_type=code, client_id=labhub-web,
redirect_uri=http://127.0.0.1:9100/callback,
scope=openid profile email, state=<16자 이상>, nonce=<16자 이상>.
필수 파라미터를 빠뜨리면 IdP 가 오류를 냅니다. state 와 nonce 는 역할이 다릅니다 - 하나는 CSRF 방지, 하나는 토큰 재생 방지입니다.
인가 코드 수신
그 URL 을 리다이렉트를 따라가지 않고 호출해 응답의 Location 헤더를
/root/oidc/callback.txt 에 저장하고, 거기서 code 값만 뽑아
/root/oidc/code.txt 에 저장합니다.
Location 의 state 는 2단계에서 보낸 값과 같아야 합니다.
브라우저 없이도 리다이렉트 응답의 Location 헤더를 읽으면 코드를 볼 수 있습니다. curl 이 리다이렉트를 따라가지 않게 해야 합니다.
토큰 교환
토큰 엔드포인트로 코드를 교환해 응답 JSON 전체를
/root/oidc/token.json 에 저장합니다.
client_id=labhub-web, client_secret=labhub-secret 을 함께 보냅니다.
access_token, id_token, token_type 이 있어야 하고 token_type 은 Bearer 입니다.
토큰 엔드포인트는 POST 이고 form 형식입니다. 인가 코드는 일회용이라 두 번 쓰면 실패합니다. 실패하면 2~3단계를 다시 하세요.
ID 토큰 페이로드 디코드
id_token 의 페이로드를 디코드해 /root/oidc/claims.json 에 저장합니다.
iss, aud, sub, exp, nonce 가 있어야 하고,
aud 는 labhub-web, nonce 는 2단계에서 보낸 값과 같아야 합니다.
JWT 는 점으로 나뉜 세 부분이고 각각 base64url 입니다. base64url 은 표준 base64 와 문자 두 개가 다르고 패딩이 없을 수 있습니다.
서명 검증 스크립트
/root/oidc/verify.sh 를 만듭니다. 인자 두 개(ID토큰 공개키PEM)를 받아
서명이 유효하면 종료코드 0, 아니면 0 이 아닌 값으로 끝냅니다.
공개키는 /opt/lab/fixtures/auth/oidc/idp-public.pem 에 있습니다.
서명 대상은 '헤더.페이로드' 문자열 전체입니다. openssl dgst 로 공개키 검증을 할 수 있고, 서명값은 base64url 디코딩이 필요합니다.
userinfo 호출
access_token 으로 userinfo 엔드포인트를 호출해
/root/oidc/userinfo.json 에 저장합니다.
sub 값이 5단계 claims.json 의 sub 와 같아야 합니다.
액세스 토큰은 Authorization 헤더에 Bearer 방식으로 보냅니다. 반환된 sub 가 ID 토큰의 sub 와 같은지 확인하는 것이 이 단계의 핵심입니다.
ID 토큰 검증 체크리스트
/root/oidc/checklist.csv 를 만듭니다. 첫 줄은 item,risk.
서명, iss, aud, exp, nonce, alg 여섯 항목이 item 에 있어야 하고,
risk 에는 그 항목을 검증하지 않으면 가능한 공격/사고를 10자 이상으로 적습니다.
각 항목이 왜 필요한지가 아니라 '검증하지 않으면 무슨 공격이 가능한지'를 적으세요. 그래야 나중에 리뷰에서 설득력이 생깁니다.