웹사이트를 만들어도...
앱을 만들더라도...
회원 기능은 필수일 정도로 꼭 필요한 기능입니다.
그렇다면 datebase와 server, 클라이언트와의 적당한 코드로 회원가입을 구현하는 것은 쉽습니다. 회원가입 form을 통해서 가입하고자 하는 회원의
{
"id": 2young@gmail.com,
"password": q1w2e3r4
}
이러한 형태로 json 데이터를 받아서 DB에 저장하면 끝! 입니다.
하지만.. 현실은 이렇게 쉽지 않습니다. 회원가입 이후 회원이 로그인한 후 회원이 필요한 기능을 수행할 때마다 서버에서는 회원이 맞는지 검증하는 시스템이 있어야합니다.
그 기능이 없다면 아무나 정보를 수정하고 취득할 수 있으니깐요 ㅠㅠ
그리하여 회원이 로그인을 하고 나면 해당 회원을 유지시켜주고 기능을 사용할 때 해당 회원임을 인증해주는 시스템이 등장했습니다.
JWT 토큰 기반 인증 방식이 나오기 전 세션 기반 인증 방식이 주로 이용됐다.
오늘의 주된 내용은 JWT에 대한 내용이다.
JSON Web Token (JWT) 은 웹표준 (RFC 7519) 으로서 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 (self-contained) 방식으로 정보를 안전성 있게 전달해줍니다.
JWT는 .을 구분자로 3가지 문자열로 구성됩니다.
Header(헤더), Payload(내용), Signature(서명)
ex) xxxxxx.yyyyy.zzzzz
전송을 위해서 Base64로 인코딩되어 있습니다. 하지만 모두 암호화 된것은 아니기 때문에 민감한 정보를 담을 때는 주의해야합니다!!
Header와 Payload는 디코딩을 하면 평문으로 해독이 가능합니다. 따라서 인증정보, 비밀번호와 같은 민감한 정보를 넣으면 안됩니다. 그러나 Signature는 복호화할 수 없습니다.
헤더는 JWT 토큰을 어떻게 해석해야 하는지를 알려주는 부분입니다.
typ, alg 두 가지 정보로 구성됩니다.
JWT 헤더를 디코딩했을 때 보여지는 예시입니다.
{
"alg": "HS256",
"typ": JWT
}
alg: 알고리즘 장식을 지정, 서명 및 토큰 검증에 사용합니다.
typ: 토큰의 타입을 지정합니다.
기본적으로 json의 형태로 name / value 쌍으로 이루어져 있습니다.
Payload에 있는 속성들을 Claim이라고 합니다.
Payload에 등록된 클레임들은 종류에 따라서 다음과 같이 구분됩니다.
1. 등록된 클레임(Registered Claim Names)
등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다
{
"ss(issuer)": 토큰 발급자,
"sub(subject)": 토큰 제목,
"aud(audience)": 토큰 대상자,
"exp(expiration)": 토큰 만료 시간 ex) 1480849147370,
"nbf(not before)": 토큰 활성 날짜 - 이 날이 지나기 전에 토큰은 활성화되지 않습니다.
"iat(issued at)": 토큰이 발급된 시간 - 토큰 발급 이후의 경과 시간을 알 수 있습니다
"jti(JWT ID)": JWT 토큰 식별자 - 중복 방지를 위해 사용
}
2. 공개 클래임(Public Claim)
사용자 정의 클레임으로 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용해야 한다.
{
"https://velog.io/@yh20studio": true
}
3. 비공개 클래임(Private Claim)
등록된 클레임도 아니며, 공개 클래임도 아닌 서버와 클라이언트 사이에 임의로 지정한 정보를 저장하기 위해 만들어진 사용자 지정 클레임입니다.
{
"username": "2young"
}
비공개 클래임을 조심해서 사용하지 않으면 등록된 클래임과 공개 클래임과 다르게 충돌이 일어날 수 있기에 주의해야 합니다.
헤더와 페이로드는 암호화 한 것이 아니라 단순히 JSON 문자열을 base64로 인코딩한 것입니다. 누구나 디코딩을 한다면 헤더와 페이로드의 내용을 볼 수 있습니다.
누군가 JWT를 탈취하여 수정한 후 서버로 보낼 수 있습니다. 이 경우에 대비해 다른 사람이 위변조 했는지 검증하기 위한 부분입니다.
서명은 헤더의 인코딩 값, 정보의 인코딩 값을 합친 후 비밀키로 해쉬를 하여 생성합니다.
이 서명에 사용되는 비밀키는 외부에 노출되지 않도록 주의해야합니다.
비밀키가 외부에 노출된다면 누구나 서버의 인증을 통과하는 signature를 만들 수 있기 때문에 인가되지 않은 요청들이 통과될 수 있습니다.
토큰 방식을 이용하여 회원인증을 구현하게 된다면 몇가지 문제가 생기게 된다.
토큰을 한번 발행하고 클라이언트 서버에 보내고 나면 이후에 서버에서는 토큰을 파괴시킬 방법이 존재하지 않는다. 보통 JWT 토큰은 클라이언트의 로컬 Storage에 저장하고 사용하게 되는데 이때 혹시나 토큰의 탈취 현상이 일어난다면 해당 토큰을 서버에서 제어할 수 없기 때문에 문제가 발생하게 된다.
따라서 토큰을 발행할 때는 무조건! 만료시간을 넣어줘야 한다.
그렇다면 보안을 위해서 만료시간을 짧게 해야할까? 막연하게 짧게 설정한다면 토큰이 만료될 때마다 사용자는 다시 로그인을 해줘야하거나 서버는 토큰을 매번 발행해야하는 번거로움이 발생할 것이다.
그렇다고 사용의 편리함과 서버의 부하를 줄이기 위해서 토큰의 만료시간을 무제한으로 늘릴 수는 없다. 보안에 취약하기 때문이다.
이를 적당히 보완하며 중간 지점을 맞추기 위해서 등장한 것이
Access Token과 Refresh Token 이다.
JWT는 일반적으로 클라이언트와 서버, 서비스와 서비스 사이 통신 시 권한 인가(Authorization)를 위해 사용하는 토큰이며, api 요청의 header에 다음과 같이 넣어서 이용한다.
{
"Authorization" : "Bearer jwt"
}
토큰은 클라이언트 측에 저장되기 때문에 서버는 완전히 Stateless하며, 클라이언트와 서버의 연결고리가 없기 때문에 확장하기에 매우 적합하다.
만약 사용자 정보가 서버 측 세션에 저장되고, 중앙 세션 관리 시스템이 없는 경우에 서버를 확장하여 분산처리 한다면, 해당 사용자는 처음 로그인 했었던 서버에만 요청을 받도록 설정을 해주어야 한다.
하지만 토큰을 사용한다면 어떠한 서버로 요청이 와도 상관이 없다. 비밀키 값만 서버들이 알고 있다면 처리하는데 문제가 없기 때문이다.
클라이언트가 서버로 요청을 보낼 때 더 이상 쿠키를 전달하지 않으므로, 쿠키 사용에 의한 취약점이 사라지게 된다. 하지만 토큰 환경의 취약점이 존재할 수 있으므로 이에 대비해야 한다.
서버 기반 인증 시스템의 문제점 중 하나인 CORS를 해결할 수 있는데, 애플리케이션과 서비스의 규모가 커지면 여러 디바이스를 호환시키고 더 많은 종류의 서비스를 제공하게 된다. 토큰을 사용한다면 어떤 디바이스, 어떤 도메인에서도 토큰의 유효성 검사를 진행한 후에 요청을 처리할 수 있다.
🎛 어플리케이션, 웹 모두 컨트롤이 가능하다는 뜻이다!
토큰에 선택적인 권한만 부여하여 발급할 수 있다.
Ex) 아마존에 구글 계정으로 로그인 할 수 있어도 아마존이 로그인한 구글 계정의 메일을 읽을 수 없습니다.
OAuth의 경우 페이스북, 구글, 카카오와 같은 소셜 계정을 이용하여 다른 웹 서비스에서도 로그인 할 수 있습니다.
사용자 인증 시스템은 웹 혹은 앱 서비스를 하는데 있어서 거의 필수라고 할 수 있습니다. 이에 세션을 이용한 방법과 JWT 토큰을 이용한 방법이 주로 이용되고 있습니다. 이때 중앙 세션 처리 시스템을 이용해서 처리하는 방법이 어려운 상황이라면, 혹은 웹사이트, 앱을 동시에 처리하는 서버를 구성하기 위해서는 JWT 토큰 방식을 이용한 인증이 더 장점이 많은 것 같습니다.
JWT를 소개합니다.
JWT란?
[Server] JWT(Json Web Token)란?
[JWT] JSON Web Token 소개 및 구조