인증(authentication)은 사용자가 자기 계정을 사용하려고 할 때 로그인을 시키는 것, 인가(authorization)는 인증을 받은 사용자가 서비스 안에서 돌아다닐 때 서버가 로그인한 사용자임을 알고 허가해주는 것이라고 생각하면 된다. 오늘은 '인가'에 대해 알아보자.
먼저 통신과 http에 대해 다시 간단히 짚고 넘어가자.
http 소통의 핵심은 request와 response가 메세지 형식으로 전달된다는 것이다. client가 server에 request를 보내면 server는 client에게 response를 보낸다. http 개별 통신은 모두 독립이기 때문에 과거 통신의 결과를 보존하지 않는다는 특징이 있다. 이러한 이유로 추가적인 방식을 사용하게 되었다. 현재는 어떠한 방식으로 소통이 이루어지는지 '로그인' '회원가입' 과정을 통해 알아보자. 로그인/회원가입은 보안에 직결되면서도 사용자들이 불편하지 않도록 해야한다는 것을 염두에 두고 공부해보자.
https://velog.io/@dabin0219/TIL-26-HTTP
http 통신의 결과가 보존되지 않기 때문에 한 번 로그인을 하면 그 상태가 유지되는 방식을 고안했다.
<알아야 할 개념>
1. 쿠키 : 브라우저에 저장되는 정보
2. 세션 : session ID를 사용해서 어떤 사용자가 서버에 로그인 된 상태가 지속되는 상태
client가 http요청(로그인)을 하면 유저 정보를 확인 후 메모리에 session id를 저장하고 해당 session id를 쿠키의 형태로 전송한다. client는 이 쿠키를 브라우저에 저정하고 다음 요청부터 session id가 저장된 쿠키를 포함해 http요청을 한다. 서버는 이 session id를 통해 유저 정보를 확인하기 때문에 별도의 로그인 요청이 필요 없어진다.
하지만 이 방식에는 허점이 있다. 에러가 나거나 해서 서버가 재부팅되어야 하는 상황이 오면 메모리에 있던 것들은 다 사라지게 된다. 그렇게 되면 모든 사용자는 다시 로그인을 해야되는 상황이 발생하게 된다. 여러 개의 서버가 운영되고 있을 때 서로 다른 서버에서는 세션 유지가 제대로 되지 않는다는 문제도 있다. 이러한 문제 없이 사용하게 만드는 것으로 JWT가 있다.
authorization에 관련된 기술이다. 위의 방식이 session id를 서버와 브라우저에 나누어 저장했다면, JWT 사용방식은 모든 정보를 토큰에 담아 보내준다.더 이상 서버에는 로그인 했다는 사실을 저장하지 않는다. 또한 secret key를 공유해 이용하는 서버가 달라져도 다시 로그인을 하지 않아도 된다.
마침표를 기준으로 세 부분으로 나뉜다.
토큰의 타입(JWT)과 해시알고리즘 정보(signature값을 만드는데 사용될 알고리즘)가 담겨 있다. 내용은 BASE64방식으로 인코딩되어 JWT 가장 첫 부분에 기록된다.
header와 payload, 그리고 서버에 감춰놓은 비밀 값(secret key)을 알고리즘에 넣고 돌리면 signature 값이 나온다. 이 암호화 알고리즘은 한쪽 방향으로만 계산이 되기 때문에 보안에 유리하다.
Base64로 디코딩해보면 JSON 형태로 여러 정보들이 들어 있다. 누가 누구한테 발급했는지, 만료시간은 언제인지, 서비스가 사용자에게 토큰을 사용해 공개하기 원하는 내용(클레임) 등이 담겨 있다. 그 외 다른 내용도 자유롭게 담을 수 있다.
BASE64로 인코딩하기 때문에 암호화되어있지 않다. 디코딩시 정보를 볼 수 있기 때문에 사용자를 식별할 수 있는 민감한 정보를 담아서는 안된다. 이 때문에 header와 signature이 존재한다.
JWT가 원본 그대로라는 것을 확일할 때 사용한다. 인코딩된 header와 payload, 그리고 secret key(별도 생성)를 header에 지정된 암호 알고리즘으로 암호화하여 전송하고, 서버에서 복호화가 가능하다.(회사마다 secret key로 특정한 문자열을 넣어놓는다)
JWT는 백엔드에서 만들고, 프론트엔드에서 토큰을 받아 브라우저에 저장한다. JWT를 사용하게 되면 통신의 순서는 아래와 같다.
http 요청이 서버측에 들어오면 서버는 유저정보를 확인한 후 해당 정보와 서버의 secret key를 사용해서 jwt를 생성해 client측으로 전송한다. client는 이를 브라우저에 저장하고 이후 jwt를 포함한 http 요청을 하게 된다. 서버는 signature을 확인 후 인가(authorization)해준다.
JWT를 사용한 Authorization 절차를 정리해보자.
세션처럼 모든 사용자들의 상태를 기억하고 있다는 것은 복잡하지만 구현되면 기억하는 대상의 상태를 제어할 수 있다는 뜻이 된다.(새로운 기기에 로그인할 경우 기존 사용하던 기기의 로그인 session을 종료할 수 있음) 하지만 JWT를 전송하면 서버는 이 발급 정보를 기록하지 않기 때문에 통제가 불가능하다. 따라서 실서비스를 구현할 때 JWT로만 로그인 기능을 구현하는 경우는 별로 없다.
이러한 결점을 보완하기 위해 다양한 방법을 시도할 수 있다. 먼저, 발급한 토큰의 만료 시간을 아주 짧게 줄 수 있다. 또는 access(만료 짧은)토큰과 refresh(만료 긴)토큰을 보내고 refresh 토큰의 상응값을 데이터베이스에 저장하는 방법을 사용할 수도 있다. 사용자는 access토큰의 수명이 다하면 refresh 토큰을 보내고 서버가 내용을 대조해보고 맞다면 새로운 access토큰을 발급하는 방식이다. 하지만 이도 access 토큰이 살아있는 동안은 통제가 불가능해 아직 한계점이 남아있긴 하다. 이러한 특성을 고려해 서비스의 종류에 따라 인증|인가의 방식을 결정하면 된다.