통합 인증의 핵심은 Zendesk가 회사 시스템으로부터 받는 로그인 요청을 신뢰할 수 있도록 하는 JSON 웹 토큰(JWT)이라는 기술입니다. 자세한 내용은 JWT(JSON 웹 토큰)로 통합 인증 설정하기를 참조하세요.
JSON 웹 토큰 인증 요청은 다음과 같은 형태입니다.
https://joeandco.zendesk.com/access/jwt?jwt=eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpYXQiOjEzNzIxMTMzMDUsImp0aSI6ODg4MzM2MjUzMTE5Ni4zMjYsIm5hbWUiOiJUZXN0IFVzZXIiLCJlbWFpbCI6InR1c2VyQGV4YW1wbGUub3JnIiwiZXh0ZXJuYWxfaWQiOiI1Njc4Iiwib3JnYW5pemF0aW9uIjoiQXBwbGUiLCJ0YWdzIjoidmlwX3VzZXIiLCJyZW1vdGVfcGhvdG9fdXJsIjoiaHR0cDovL21pdC56ZW5mcy5jb20vMjA2LzIwMTEvMDUvQmFybmFieV9NYXR0X2Nyb3BwZWQuanBnIiwibG9jYWxlX2lkIjoiOCJ9.Zv9P7PNIcgHfxZaMwQtMpty3TZnmVHRWcsmAMM-mNHg
위의 요청을 브라우저의 URL 표시줄에 넣기만 하면 Zendesk 인스턴스에 로그인됩니다.
아주 간단하죠. 그 밖에는 필요하지 않습니다. 서버끼리 뒤에서 소통하는 일 없이 URL에서만 이뤄집니다.
원격 인증 스크립트가 만든 다음에 사용자를 보냅니다.
그럼 토큰 구조를 분석해 봅시다.
https://joeandco.zendesk.com/access/jwt?jwt=
첫 부분은 원격 인증 요청의 대상이 되는 URL 엔드포인트입니다. 그 다음은 매개변수를 추가한다는 의미의 물음표(?)가 붙습니다. 매개변수는 대상 엔드포인트(https://joeandco.zendesk.com/access/jwt)에 있는 스크립트로 일부 정보를 전달합니다. 이들 매개변수를 허용하도록 스크립트를 설계해야 하기 때문에 임의의 주소가 아니라 /access/jwt로 보내야 합니다.
물음표 뒤에 오는 ‘jwt’는 매개변수 이름이고, ‘=’ 뒤에 오는 문자열이 매개변수 값임을 나타냅니다.
나머지는 데이터입니다. 자세히 보면 여러 청크를 마침표로 구분했음을 알 수 있습니다. 그럼 청크를 하나씩 살펴보겠습니다.
청크 1: JWT 머리글
첫 번째 청크는 JWT 머리글로, JWT 요청이라는 점과 어떤 종류의 해시 알고리즘이 사용되었는지를 나타냅니다(추후 내용 추가 예정).
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
여기서 이 부분 및 나머지 모든 데이터에 대해 대표적으로 알 수 있는 바는 바로 base64 인코딩이라는 점입니다. 실제로는 암호화가 아니기 때문에 다음과 같은 도구로 쉽게 디코딩할 수 있습니다.
- 웹 도구: http://www.base64decode.org/
- 터미널: http://drewsymo.com/how-to/quick-and-simple-base64-encode-on-mac-osx-terminal/
디코딩된 문자열은 다음 형태를 지닙니다.
{"typ":"JWT",
"alg":"HS256"}
이는 JSON 구조를 띠며, type: JWT
및 Algorithm: HMAC SHA 256
를 효과적으로 의미하는 2개의 키 값 쌍으로 구성되어 있습니다. SHA 256은 미국 국가 안보국에서 디자인한 256비트 암호화입니다. 이를 사용하여 앞으로 설명할 세 번째 청크인 서명을 만듭니다.
청크 2: JWT 클레임 집합/페이로드
페이로드가 있어서 더 긴 두 번째 청크를 가리켜 ‘JWT 클레임 집합’이라고 합니다.
eyJpYXQiOjEzNzIxMTMzMDUsImp0aSI6ODg4MzM2MjUzMTE5Ni4zMjYsIm5hbWUiOiJUZXN0IFVzZXIiLCJlbWFpbCI6InR1c2VyQGV4YW1wbGUub3JnIiwiZXh0ZXJuYWxfaWQiOiI1Njc4Iiwib3JnYW5pemF0aW9uIjoiQXBwbGUiLCJ0YWdzIjoidmlwX3VzZXIiLCJyZW1vdGVfcGhvdG9fdXJsIjoiaHR0cDovL21pdC56ZW5mcy5jb20vMjA2LzIwMTEvMDUvQmFybmFieV9NYXR0X2Nyb3BwZWQuanBnIiwibG9jYWxlX2lkIjoiOCJ9
여기에는 타임스탬프, 임의 값, 사용자 이름, 이메일 주소, 외부 ID 및 일부 태그가 들어 있습니다. 사용 가능한 옵션도 더 많습니다. 다시 말하지만 base64 디코딩으로 페이로드를 파악하세요. 이해하기 쉽도록 라인을 나눠서 설명하겠습니다.
{
"iat":1372113305,
"jti":8883362531196.326,
"name":"Test User",
"email":"tuser@example.org",
"external_id":"5678",
"organization":"Apple",
"tags":"vip_user",
"remote_photo_url":"http://mit.zenfs.com/206/2011/05/Barnaby_Matt_cropped.jpg",
"locale_id":"8"
}
필수
IAT
첫 번째 키는 iat로, 발급 시간을 의미합니다. 1970년 1월 1일부터의 시간을 표준 UNIX로 초 단위 형식으로 기록한 타임스탬프입니다. 타임스탬프는 소수점이 아니라 정수로 표시하고 시간대는 UTC여야 합니다. 또한 Zendesk 서버에서 받은 현재 시간으로부터 3분 이내여야 하는데 그래야만 어느 한 요청이 생긴 지 3분이 지난 후에 사용되는 상황을 막는 자체 소멸 메커니즘을 원격 인증 요청마다 가동할 수 있습니다.
JTI
두 번째 키는 jti로, JSON 토큰 ID를 의미하는데 그냥 임의 문자열입니다. 이 계정에 다시 사용될 가능성이 거의 없는 긴 임의 문자열이어야 합니다. 혹시라도 다른 인증 요청에 다시 사용되는 경우에는 요청이 실패하게 되고 삭제 가능한 키가 됩니다. 이처럼 임의 값을 의무적으로 포함시켜야 서로 같은 인증 요청이 생기는 상황이 생기지 않습니다. 그래야만 유효한 요청 URL이 다시 사용되는 경우를 방지할 수 있습니다. 예를 들어 누군가 내 컴퓨터나 네트워크에 맬웨어를 설치한 다음 트래픽을 로그하기 시작했다고 가정해 보세요. 그러면 내가 방문하는 모든 URL을 볼 수 있게 됩니다. Zendesk 로그인 URL을 상대방이 확보하게 되면 이 단일 사용 키가 없이도 3분 안에 마치 해당 사용자인 것처럼 로그인하는 상황이 벌어집니다.
이름
그 다음은 사용자의 전체 이름으로 띄어쓰기를 포함합니다. 전에 있던 이름 집합과 다르더라도 Zendesk에서 받은 내용 그대로 사용자 이름이 됩니다.
이메일
그 다음은 사용자의 이메일로, 외부 ID를 받지 않은 경우* 사용자의 고유 ID로 사용됩니다. 즉 이메일과 외부 ID 둘 다 받으면 우선 ID와 일치하는지 확인해 보고 이 이메일을 해당 사용자의 ID로 업데이트한다는 의미입니다.
*참고: ‘외부 ID의 업데이트를 허용하시겠습니까?’ 옵션이 Zendesk에서 사용 설정된 경우에는 외부 ID를 받더라도 계속 이메일을 사용합니다. 이메일과 외부 ID가 서로 다르면 ID를 변경합니다.
선택 사항
외부 아이디
외부 ID는 사용자 식별을 위해 이메일(위에서 설명함) 대신 사용 가능한 ID입니다.
Organization
조직 값을 전달하여 조직에 사용자를 추가할 수도 있습니다. 해당 조직이 이미 존재해야 하고 조직 이름이 정확히 일치해야 합니다. 그렇지 않으면 작업이 수행되지 않습니다.
Tags
tags 키를 사용하여 로그인 중인 사용자에 대해 태그를 설정할 수 있습니다. 키는 사용자의 기존 태그를 지정한 태그로 대체하므로 조심해서 사용해야 합니다. 빈 태그 매개변수를 전달하면 사용자에게서 모든 태그를 제거하게 됩니다.
원격 사진 URL
remote_photo_url 값도 전달할 수 있는데, 이 값은 사진을 포함한 공개 URL을 허용하고 이 사진을 사용자의 프로필 사진으로 설정합니다.
로캘(언어)
locale_id 값을 전달하여 Zendesk에서 인증된 사용자의 언어를 설정 또는 업데이트할 수 있습니다. 이 값은 Zendesk에서 현재 활성화되어 있는 로캘과 일치하는 숫자여야 합니다. 로캘을 찾으려면 Zendesk API의 다음과 같은 locales.json 엔드포인트를 사용하면 됩니다. http://developer.zendesk.com/documentation/rest_api/locales.html#list-locales
사용자 필드(위의 예에는 나오지 않음)
각각의 필드 키 및 값에 대한 키 값 쌍을 포함하는 JSON 개체여야 합니다. 사용자 필드 인터페이스에서 필드 키를 찾거나 정의할 수 있습니다. 단, 사용자 지정 사용자 필드만 전달할 수 있습니다.
예:
"user_fields": {"checked": false,"date_joined": "2013-08-14T00:00:00+00:00","region": "EMEA","text_field": null}
확인란은 부울 값을 사용하고, 날짜 코드는 위의 예를 따르며, 드롭다운은 해당 옵션의 이름을 허용합니다. 텍스트 필드는 문자열을 허용합니다.
전화번호(위의 예에는 나오지 않음)
전화번호 ID에 대한 문자열을 허용합니다. 반드시 사용할 수 있는 형식으로 전화번호를 지정합니다. 자세한 내용은 사용할 수 있는 전화번호 형식을 참조하세요.
청크 3: JWS 서명
요청을 구성하는 마지막 청크는 암호화되어 있는데, 기본적으로 공유 비밀과 더불어 위의 모든 정보(iat, jti, 이름, 이메일 등)를 바탕으로 암호화된 문자열을 만드는 것으로 그다지 어렵지 않습니다. JWT 표준에 따른 암호화된 문자열 청크, 즉 체크섬이 생기는데 이것이 바로 JWS 서명입니다.
그렇다면 얼마나 안전할까요?
암호화된 유효한 문자열을 만들기 위해서는 공유 비밀을 알아야 합니다.
암호화를 거친 문자열은 데이터와 키를 역으로 알아낼 수 없도록 설계되었으므로, 데이터 콘텐츠가 있더라도 키를 추출해 내기가 실질적으로 불가능합니다.
하지만 사용자도 키가 있고 Zendesk에도 키가 있기 때문에 암호화한 데이터와 똑같은 데이터를 암호화 없이 보내면 Zendesk에서 직접 서명을 만든 다음 해당 사용자가 보낸 데이터와 일치하는지 확인합니다.
또한 전송 중인 데이터를 아무도 건드릴 수 없도록 합니다.
다음과 같이 의사코드에서 서명을 만들었습니다.
URLBase64Encode(
HMAC-SHA256(
URLBase64Encode( header_json ).URLBase64Encode( payload_json )
)
)
인코딩된 머리글과 페이로드의 HMAC-SHA256을 만들 때 공유 비밀을 포함시킵니다.
그러면 이렇게 됩니다.
Zv9P7PNIcgHfxZaMwQtMpty3TZnmVHRWcsmAMM-mNHg