Keycloak 과 기업 인증 · OIDC 토큰과 검증 · 실습
JWT 파싱과 서명·클레임 검증
목표
JWT 를 손으로 뜯어 서명을 검증하고 클레임을 확인하면서, 라이브러리가 대신 해 주던 일이 정확히 무엇인지 이해한다.
왜 중요한가
JWT 검증은 라이브러리 한 줄이라 내부를 모르고 지나치기 쉽습니다. 그런데 그 한 줄에 옵션을 잘못 주면 조용히 뚫립니다. 대표적으로 aud 검사를 켜지 않으면, 같은 인증 서버가 발급한 다른 API 용 토큰이 우리 API 에서 통과합니다. alg 를 검증하지 않으면 none 알고리즘으로 서명 없는 토큰이 통과한 사고가 실제로 있었습니다. 그리고 이 실습에서 마지막에 재는 숫자가 중요합니다 — introspection 은 매 요청 네트워크 왕복이고 로컬 검증은 CPU 연산입니다. 마이크로서비스 열 개가 각각 물어보면 인증 서버가 병목이 되기 때문에, 실무는 액세스 토큰을 5~15분으로 짧게 두고 로컬 검증을 씁니다. 취소 반영 지연을 토큰 수명만큼으로 제한하는 거래입니다.
단계
1. /root/kc/wait.sh 로 http://127.0.0.1:8080/realms/master 가 200 이 될 때까지 최대 180초 기다린다. /root/kc/ready.txt 에 ready_seconds=<정수> 를 적는다.
2. /opt/fixtures/kc/realm-info.env 의 값으로 토큰을 발급받아 /root/kc/token.txt 에 액세스 토큰만 한 줄 저장한다. 점이 2개인 문자열이어야 한다.
3. /root/kc/decode.py 로 헤더를 /root/kc/header.json, 페이로드를 /root/kc/claims.json 에 저장한다. 둘 다 유효한 JSON 이어야 한다.
4. /root/kc/claimcheck.txt 에 iss=<값> aud=<값> sub=<값> exp_in=<남은초> 를 적는다. exp_in 은 0보다 커야 한다.
5. http://127.0.0.1:8080/realms/labhub/protocol/openid-connect/certs 에서 JWKS 를 받아, 헤더의 kid 와 일치하는 키를 /root/kc/jwk.json 에 저장한다. kty 가 RSA 여야 한다.
6. /root/kc/verify.py 로 서명을 검증하고 /root/kc/verify.out 에 signature=valid alg=<값> 을 적는다.
7. 페이로드를 변조한 토큰으로 같은 검증을 돌려 /root/kc/tamper.out 에 signature=invalid 를 적는다.
참고
- Base64URL 디코드 시 패딩 보정:
s + '=' * (-len(s) % 4) - 서명 대상은
<header_b64>.<payload_b64>문자열 그대로입니다. - ID 토큰과 액세스 토큰은 다릅니다. API 호출에는 액세스 토큰을 씁니다.
- 흔한 실수 1:
aud를 확인하지 않는 것 — 다른 API 용 토큰이 통과합니다. - 흔한 실수 2: 헤더의
alg를 확인하지 않는 것 —none알고리즘 우회의 통로가 됩니다.
단계 7개
- Keycloak 준비될 때까지 기다리기
- 토큰 발급받기
- 헤더와 페이로드 디코드하기
- 필수 클레임 확인하기
- JWKS 에서 서명 키 찾기
- 서명 검증하기
- 변조 토큰이 거부되는지 확인하기