52 lines
5.5 KiB
JSON
52 lines
5.5 KiB
JSON
{
|
|
"kind": "QUESTION",
|
|
"title": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
|
|
"slug": "refresh-rotation-replica-contention",
|
|
"summary": "realm이 refresh token rotation과 재사용 허용 0회를 쓴다. 두 replica가 같은 refresh token으로 동시에 갱신할 수 있고, 그때 두 번째 사용이 거부될 가능성이 있다. 실제 Keycloak 응답과 session 영향은 아직 재현하지 않았다.",
|
|
"questionStatus": "OPEN",
|
|
"options": [
|
|
{
|
|
"title": "분산 lock으로 갱신을 직렬화한다",
|
|
"description": "한 replica만 갱신하고 나머지는 끝나기를 기다렸다가 결과를 읽게 되어서 재사용 거부가 아예 생기지 않는다.\n\n분산 lock을 사용하면 refresh 구간을 직렬화할 수 있다. lock 저장소의 가용성, lock 만료, 재진입, lock 보유 process 종료 상황까지 함께 처리해야 한다."
|
|
},
|
|
{
|
|
"title": "각자 갱신하고 실패는 재시도로 처리한다",
|
|
"description": "구현이 가장 단순하다. 지는 쪽이 거부를 받으면 저장소에서 최신 token을 다시 읽어 재시도한다는 전제인데, 이 재시도가 성립하는지는 아직 확인하지 않았다.\n\nreuse detection 정책에 따라 같은 refresh token의 두 번째 사용이 token family 전체에 영향을 줄 수 있다. 이 경우 단순 retry로 끝나지 않고 재인증이 필요할 수 있다. 갱신에 성공한 replica가 새 token을 저장하기 전에 다른 replica가 다시 조회하는 순서도 별도로 재현해야 한다."
|
|
},
|
|
{
|
|
"title": "갱신 전용 경로를 하나 둔다",
|
|
"description": "refresh를 전담하는 구성요소 하나만 refresh token을 사용하고 다른 replica는 갱신 결과를 조회하도록 구성할 수 있다.\n\nrefresh 전담 구성요소가 중단되면 access token 만료 이후 갱신을 수행할 주체가 없어지므로 해당 구성요소의 가용성과 복구 방식이 중요해진다."
|
|
},
|
|
{
|
|
"title": "제약상 제외 — 재사용 허용을 늘린다",
|
|
"description": "짧은 유예를 주면 경쟁이 저절로 해소되고 코드도 고칠 필요가 없다. 다만 rotation과 재사용 0회는 이 질문이 바꾸지 않기로 한 realm 설정이다. 훔친 refresh token을 쓸 수 있는 창도 같이 늘어난다.\n\n비교 대상으로만 남긴다."
|
|
}
|
|
],
|
|
"nextValidation": "저장소를 공유한 뒤에 재현한다.\n\n1. replica 두 대에서 같은 사용자로 access token 만료 직후 동시에 요청을 보낸다.\n2. 이긴 쪽과 지는 쪽의 응답을 각각 기록한다.\n3. 지는 쪽이 저장된 새 token으로 재시도해 성공하는지 본다.\n4. 지는 쪽 사용자 화면에 무엇이 보이는지 기록한다.\n5. lock을 넣은 구성과 안 넣은 구성을 같은 입력으로 비교해 실패율과 지연을 잰다.\n\n실패가 사용자에게 노출되면 lock을 고르고, 노출되지 않으면 재시도로 둔다.",
|
|
"facts": [
|
|
"realm은 refresh token rotation과 재사용 허용 0회를 쓰게 되어서, 한 번 갱신하면 이전 refresh token은 바로 무효가 된다.",
|
|
"커밋된 테스트는 새 refresh token 발급과 이전 token 거부, revocation 뒤 refresh 실패를 확인하는데 모두 한 주체가 순서대로 부르는 경우다.",
|
|
"authorized client manager에는 refresh-token provider가 구성되어 있어 access token 만료 시 refresh를 시도할 수 있다.",
|
|
"다만 만료를 기다려 실제 갱신이 성공하고 새 token이 저장되는지까지는 확인하지 않았다.",
|
|
"현재 authorized client 저장소는 process-local이라 replica가 같은 refresh token 상태를 공유하지 않는다. 따라서 이번 단일 인스턴스 검증에서는 동시 refresh 경쟁을 재현하지 않았다.",
|
|
"이미 발급된 access token은 만료 전까지 API에서 계속 통하기 때문에, 갱신이 실패해도 그동안은 화면이 정상으로 보이게 된다."
|
|
],
|
|
"assumptions": [
|
|
"운영에서는 replica가 둘 이상이고 저장소를 공유해 같은 authorized client 항목을 보게 된다.",
|
|
"두 replica가 비슷한 시각에 만료를 만나면 각각 갱신을 시도하게 된다."
|
|
],
|
|
"unknowns": [
|
|
"같은 refresh token으로 두 replica가 동시에 갱신하면 어느 쪽이 이기고 지는 쪽은 무엇을 받게 되는가.",
|
|
"재사용 허용 0회에서 지는 쪽의 요청이 사용자 화면에 어떻게 보이게 되는가. 로그인 만료로 보이는가 일시적 오류로 보이는가.",
|
|
"지는 쪽이 저장소에서 새 token을 다시 읽어 재시도하면 성공하게 되는가, 아니면 재인증이 필요해지는가.",
|
|
"갱신을 한 곳에서만 할 것인가, 각자 하게 두고 실패는 재시도로 처리할 것인가.",
|
|
"lock을 쓴다면 어디에 두고 얼마나 잡게 되는가. 잡은 채로 프로세스가 내려가면 어떻게 푸는가.",
|
|
"갱신 실패를 로그인 만료와 구분해서 표시할 수 있게 되는가."
|
|
],
|
|
"constraints": [
|
|
"rotation과 재사용 0회는 이미 realm 설정이라서 이 전제를 바꾸지 않고 답해야 한다.",
|
|
"이미 발급된 access token은 만료 전까지 사용할 수 있으므로 refresh 실패는 즉시 보이지 않을 수 있다. 재현 테스트는 access token 만료 직후에 맞춰 실행한다.",
|
|
"이 경쟁은 저장소를 공유한 뒤에야 재현되기 때문에 저장소 결정이 이 질문보다 앞서게 된다."
|
|
]
|
|
}
|