인증 알아보기 (JWT)
토큰 기반 인증
이전 글에서 살펴본 세션을 사용하는 인증은 scale out 환경에서 처리가 복잡해진다는 문제가 있었다.
토큰 기반의 인증을 사용하면 이러한 문제점을 해결할 수 있다.
세션 기반 인증은 인증 정보를 서버에 저장하는 방식으로 구현되지만,
토큰 기반 인증은 쿠키와 비슷하게 인증 정보를 클라이언트가 가진다.
인증 정보는 토큰의 형태로 쿠키 혹은 브라우저의 로컬 스토리지에 저장된다.
토큰 기반 인증에서는 사용자가 가진 토큰을 HTTP의 Authorization 헤더에 실어서 보낸다.
우선 Authorization 헤더에 대해 좀 더 알아보자.
HTTP 인증 프레임워크
HTTP 인증 프레임워크는 RFC 7235에 정의되어 있다.
HTTP에서 인증은 일반적으로 다음과 같이 동작한다.
- 서버는 클라이언트에게 401(Unauthorized) 응답 코드를 가지고 응답한다.
- 인증을 원하는 클라이언트는 Authorization 요청 헤더 필드에 정보를 포함하여 인증을 수행할 수 있다.
Authorization 인증 헤더는 다음과 같은 형태로 사용된다.
Authorization: <type> <credentials>
type은 인증 스키마, credentials는 자격 증명을 의미한다.
✔️ Bearer 인증
Bearer 인증은 Token 인증이라고도 부른다.
인증 스키마의 종류에는 IANA에서 관리하는 것과, Amazon AWS와 같은 호스트 서비스에서 제공하는 것들이 있다.
Bearer은 인증 스키마의 한 종류이며, 자세한 내용은 RFC 6750에 정의되어 있다.
Bearer는 소유자라는 뜻으로, '해당 토큰을 소유한 소유자에게 권한을 부여해줘"라는 의미로 이름을 붙였다고 한다.
Bearer 인증 방식에서는 자격 증명으로 token을 사용한다.
Authorization: Bearer <token>
Bearer 인증은 원래 RFC 6750의 Oauth2.0의 일부로 만들어졌지만, 단독으로 사용되기도 한다.
✔️ Bearer 토큰
Bearer 토큰은 OAuth 프레임워크에서 액세스 토큰으로 사용하는 토큰의 유형이다.
토큰의 형태는 인증 서버에서 정의하기 나름이지만, 대부분 JWT를 사용한다.
액세스 토큰에 대한 내용과, 주의 사항은 다음 링크를 참고하자.
OAuth 인증 프로토콜이 아닌 일반적인 웹 애플리케이션에서 로그인을 구현하는 경우에도 대체로 Bearer을 사용하는 것을 확인할 수 있다.
물론 프로젝트 내부에서 규약을 따로 정의하여 사용한다면 굳이 Bearer을 붙일 필요는 없으나,
그래도 베스트 프랙티스를 따르는 편이 여러모로 좋다고 생각한다.
JWT (JSON Web Token)
이제 토큰의 종류로써 자주 사용되는 JWT에 대해 알아보자.
JWT는 RFC 7519에 정의되어 있다.
✔️ JWT 구조
JWT는 header(헤더), payload(내용), signature(서명)으로 이루어져 있으며,
이는 아래와 같이 .을 통해 구분되어진다.
✔️ Header
Header는 아래 두가지 정보를 지닌다.
- typ: 토큰의 타입을 지정하는데, JWT에서는 "JWT"이다.
- alg: signature(서명)을 암호화하는 알고리즘을 지정한다.
✔️ Payload
Payload 부분에는 토큰에 담을 정보가 들어있다.
여기에 담는 정보 각각을 클레임(claim) 이라고 부르고,
이는 key-value 형식으로 이루어진다.
토큰에는 여러개의 클레임들을 넣을 수 있고,
이러한 값들은 특별한 제약 없이 개발자의 마음대로 추가할 수 있다.
JWT에서 보편적으로 사용되는 클레임은 다음과 같다.
(이들을 registered 클레임이라 부른다.)
- iss: 토큰 발급자 (issuer)
- sub: 토큰 제목 (subject)
- aud: 토큰 대상자 (audience)
- exp: 토큰의 만료시간 (expiraton).
- NumericDate 형식으로 작성된다.(예: 1480849147370)
- 만료시간 이후에는 토큰을 사용할 수 없다.
- nbf: 토큰의 활성 날짜.
- NumericDate 형식으로 작성된다.
- 이 날짜 이전에는 토큰을 사용할 수 없다.
- iat: 토큰이 발급된 시간 (issued at)
- jti: JWT의 고유 식별자. (issuer가 여러 명일때 구분하기 위해 사용)
이러한 클레임들은 꼭 포함할 필요는 없으며, 상황에 따라 필요하다면 추가하면 된다.
위 클레임 외에도 필요한 클레임이 있으면 추가하면 된다.
예를 들어 인증을 위해 회원 Id를 추가하는 것도 가능하다.
이때, 제일 주의할 점은 payload에 민감한 정보(사용자 개인정보, 아이디 등)를 담아서는 안된다는 것이다.
JWT는 그냥 Json을 디코딩 한 값이므로, JWT에 민간한 정보를 담았다가 탈취를 당한다면 그대로 해당 정보가 털리는 것이다.
따라서 Payload에는 노출되어도 상관 없는, 단순히 식별을 하기 위한 정보만을 담아야 한다.
(위에서 사용한 memberId가 그 예시이다.)
✔️ Signature
Signature는 'header를 인코딩한 값 + payload를 인코딩한 값'을 비밀키(secret-key)를 사용하여 암호화하여 생성된다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
이때 Secret-Key를 base64로 인코딩하여 사용하기도 한다.
이를 통해 payload가 변조된 JWT를 확인할 수 있는데, 과정은 다음과 같다.
- 클라이언트가 서버에 JWT를 전달한다.
- 서버는 secret key를 가지고 signature를 복호화한다.
- 복호화한 signature에서 base64UrlEncode(header)가 JWT의 헤더와 일치하는지,
base64UrlEncode(payload)가 JWT의 페이로드와 일치하는지 확인하여 일치하는 경우에만 허용한다.
토큰 기반 인증의 장단점
세션을 사용했을 때의 문제점, 즉 수평 확장(Scale out)이 어렵다는 문제를
토큰 기반 인증(Bearer 인증)을 사용하면 쉽게 해결할 수 있다.
그러나 토큰이 탈취되는 경우 이를 탈취한 사용자가 해당 토큰을 통해 바로 리소스에 접근이 가능하다는 단점이 있다.
이를 방지하기 위해서 토큰의 노출이 발견되면 해당 토큰을 사용할 수 없도록 처리하는 추가 작업이 필요하다.
또한 토큰에 담긴 내용은 누구나 볼 수 있으므로, 토큰에는 절대 민감한 정보를 담지 않아야 한다.