본문 바로가기

세션과 토큰 기반 인가(Authorization), JWT란?

JWT에 대해 알기 위해서는 우선 인증과 인가의 개념에 대해서 알아야한다.

인증과 인가

(출처: http://www.opennaru.com/opennaru-blog/jwt-json-web-token-with-microservice/)


인증(Authentication) - 특정 권한이 있는 사용자임을 "인증"받는 것 즉, 회원가입과 로그인

인가(Authorization) - 인증(Authentication)을 받은 사용자가 서비스의 여러 기능을 사용할 때 로그인이 되어있음을 확인하고 "인가"를 받는다.

 

이중 JWT는 인가(Authorization)에 연관된 기술이다.

 

로그인 상태를 유지하기

 

웹사이트 서버가 요청을 받을 때마다 해당 사용자가 로그인, 인증과정을 거친 상태인지 확인을 해서 그에 따라 로그인이 필요한 기능들(ex. 이메일 접속, 댓글 작성)의 허용 여부를 결정해 응답을 해주어야한다.

 

아이디랑 비밀번호를 브라우저 저장소에 저장한 다음에 요청이 있을 때마다 로그인을 하는 방식으로 이러한 기능들을 허용해 주면 간단하겠지만 이러한 방법을 사용하기에는 로그인이 꽤 무거운 작업이라는 걸림돌이 있다.

로그인을 위해서는 데이터베이스에 저장된 사용자 계정의 해시값 등을 꺼내온 다음 이것들이 사용자의 암호를 복잡한 알고리즘을 통해 계산한 값과 일치하는지 확인하는 과정을 거쳐야하기 때문에 시간과 자원을 많이 소모한다. 또한 요청마다 아이디와 패스워드가 계속해서 보내지는것 또한 보안상 위험하다.

 

세션

 

세션 기반의 인가(출처: https://dev.to/thecodearcher/what-really-is-the-difference-between-session-and-token-based-authentication-2o39)

 

이러한 문제점을 해결하기 위한 전통적인 기술로는 "세션"이 있다. 사용자가 로그인에 성공하면 서버는 "세션 표딱지"라는 것을 출력한다. 이 표딱지를 반으로 나누어 반은 사용자에게 반은 자신의 메모리에 올리게 된다. 경우에 따라서는 하드디스크나 데이터베이스에 넣어놓기도 한다.

 

표 반쪽을 받은 사용자의 브라우저가 이 표를 Session ID라는 이름의 쿠키로 저장을 하고 이 브라우저는 웹사이트에 요청을 보낼 때마다 이 표딱지를 실어보낸다. 이렇게 받은 표딱지 반쪽이 요청에 실려오면 서버는 이를 메모리에서 맞는 나머지 반쪽 짝이 있는지 확인하고 이것이 확인되면 인가(Authorization)을 해준다. 이처럼 이 Session ID를 사용해 어떤 사용자가 서버에 로그인 되어있는 상태가 지속되는 상태를 '세션'이라고 한다.

 

세션 방식의 단점

이러한 세션 방식에는 몇가지 허점이 존재한다.

 

- 메모리에 세션 표딱지의 반쪽들을 올려두면 사용자가 많아질 경우 메모리가 부족해진다.

- 서버에 문제가 생겨서 재부팅되면 메모리에 있던 것들이 전부 날아가는 문제가 있다.

- 세션 표딱지의 반쪽을 하드에 넣게되면 속도가 느려지는 문제가 있다.

- 서비스가 커져 로드밸런싱을 위해 서버가 여러대가 되면 모든 서버의 메모리에 반쪽 표딱지가 존재하는것이 아니기 때문에 세션의 유지에 어려움이 있다.

 

보완법

 

이러한 허점을 보완하기 위한 방법으로는 다른 방법으로는 서버가 가지고 있어야 할 표딱지 반쪽을 속도가 많이 느려질 것을 감수하고 데이터베이스에 넣어두거나 더 흔히는 레디스나 MemCached같은 메모리형 데이터베이스 서버를 따로 두어서 올려두기도 한다.

 

서버가 복잡한 구성과 환경에서 어떤 상태를 기억해야된다는 것이 설계하기 머리아플 수 있기 때문에 이러한 부담없이 이 인가(Authorization)을 구현하기 위해 고안된 것이 '토큰 방식'인 JWT이다.

 

JWT(JSON Web Token)

 

토큰 기반 인가(출처: https://dev.to/thecodearcher/what-really-is-the-difference-between-session-and-token-based-authentication-2o39)

 

JWT를 사용하는 서비스에서는 사용자가 로그인을 하면 토큰이라는 표를 출력해서 건내준다. 위의 세션방식과의 차이는 세션 방식은 세션 표딱지를 반으로 나누었지만 JWT는 방식은 토큰을 찢지 않고 그대로 준다. 서버가 무언가를 기억하고 않다는 이야기이다.

 

사용자가 받는 토큰은 아래와 같이 생겼다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey
JzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva
G4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxw
RJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5

이는 인코딩 또는 암호화된 3가지 데이터를 이어붙인 모습이다.

중간에 점(마침표)를 기준으로 세 부분으로 나뉘는데 각각 헤더(header), 페이로드(payload), 서명(verify signature)으로 구분된다.

 

페이로드(payload)

가운데 부분인 페이로드를 Base64로 디코딩해보면 아래와 같이 JSON형식으로 여러가지 정보가 들어있다.

{
  "sub": "1234567890", //subject: whom the token refers to
  "name": "John Doe",
  "iat": 1516239022 //issued at: seconds since unix epoch
}

이 토큰을 누가 누구에게 발급했는지, 이 토큰이 언제까지 유효한지 그리고 서비스가 사용자에게 이 토큰을 통해 공개하기 원하는 내용(ex. 사용자의 닉네임, 서비스상의 레벨, 관리자 여부 등)을 서비스 측에서 원하는 대로 담을 수 있다.

이렇게 토큰에 담긴 사용자 정보 등의 데이터를 Claim이라고 한다.

이런 페이로드 부분이 특별한 암호화과정을 거친것이 아니라 Base64로 인코딩되어있기 때문에 사용자가 쉽게 디코딩해서 이 내용을 볼 수 있을 것이고 이를 조작해서 악용할 여지가 있기 때문에 이를 방지하기 위해 헤더(header)와 서명(verify signature)파트가 존재한다.

 

헤더(header)

헤더(header)를 디코딩하여 살펴보면 2가지 정보가 담겨있다.

{
  "alg": "HS256",
  "typ": "JWT"
}

typ(type), 즉 토큰의 타입은 언제나 JWT가 들어간다. 다른 하나는 alg(algorithm)인데 여기에는 서명값을 만드는데 사용될 알고리즘이 저장된다. 여기에는 HS256등의 여러 암호화 방식 중 하나를 지정할 수 있다. 헤더와 페이로그 그리고 '서버에 감춰놓은 비밀 값' 이 셋에 이 임호화 알고리즘을 적용시키면 서명값이 나온다.

 

암호화 알고리즘은 단방향으로 진행되어 복호화할 수 없기 때문에 '서버에 감춰놓은 비밀 값'을 알 수 있는 방법이 없다. 만약 사용자가 토큰의 글자 하나를 바꾼다면 서명값이 완전히 달라지기 때문에 페이로드를 수정하여 유효한 서명값이 나오려면 서버가 숨겨둔 비밀키를 알고 있어야 하기 때문에 이를 알지 못하는 사용자는 페이로드를 조작할 수 없다.

즉, 서버는 요청에 토큰값이 실려들어오면 헤더와 페이로드를 '서버의 비밀 키'와 함께 돌려 계산된 결과값이 서명값과 일치하는 결과가 나오는지 확인하기 때문에 이 '비밀키'를 알지못하는 사용자의 조작된 요청이 거부되는 것이다.

 

이러한 방식을 사용하면 서버는 '서버의 비밀 키'만 가지고 있으면 사용자들의 상태를 따로 저장해둘 필요없이 요청이 들어올때마가 토큰을 확인하여 인가(Authorization)을 해줄 수 있다.

 

JWT와 같이 서버측에 상태 정보를 저장하지 않는 형태는 stateless하다고 한다. 반대로 세션은 상태 정보를 저장하기 때문에 stateful하다고 한다.

 

JWT의 단점

- 세션처럼 stateful해서, 모든 사용자들의 상태를 기억하고 있다는 것은 구현하기 부담되고 고려사항도 많지만, 이를 구현하기만 하면 기억하는 대상의 상태를 언제든 제어할 수 있다는 의미이다. (ex. 한가지 기기에서만 로그인을 하기 위해 스마트폰에서 로그인하면 기존에 로그인한 PC에서 로그아웃되도록 기존 세션을 종료) 하지만 JWT는 이러한 통제가 불가능 하다.

- 토큰은 통제가 불가능하기 때문에 토큰이 탈취된 경우 토큰을 무효화할 방법이 없다.

- 위와 같은 이유로 실 서비스중에 JWT만으로 인가를 구현하는 곳은 생각보다 많지 않다.

 

보완법

 

위의 단점을 나름대로 보완하기 위한 방법들이 있는데, 만료시간을 가깝게 잡아서 토큰의 수명을 짧게 주는 것이다.

하지만 이러면 얼마 지나지 않아 로그인을 다시 해야하는데 이 때문에 로그인을 하고 나면 토큰을 두개 발급해준다.

하나는 수명이 짧은 access토큰이랑 다른 하나는 보통 2주정도의 긴 기간으로 잡혀있는 refresh토큰이다.

access 토큰과 refresh 토큰을 발급하고 클라이언트에게 보내고 나서 refresh토큰은 상응값을 데이터베이스에 저장한다. 클라이언트는 access토큰의 수명이 다하면 refresh토큰을 보낸다. 서버는 이를 데이터베이스에 저장된 값과 대조해보고 맞다면 새로운 access토큰을 발급해준다. 이제 이러한 refresh토큰만 안전하게 관리된다면 이 refresh토큰이 유효할동안은 access 토큰이 만료되어도 다시 로그인할 필요 없이 새로 발급을 받을 수 있다. 이러한 방법을 사용하면 access토큰이 탈취되어도 금방 만료되어 사용할 수 없게 되고 refresh토큰을 데이터베이스에서 삭제하여 토큰 갱신을 불가능하게하여 강제로 로그아웃을 시킬 수 있다. 하지만 이러한 방식은 짧은 시간이라도 access토큰이 유효한 동안은 이를 차단할 방법이 없기 때문에 완벽한 해결책은 아니다.

 

이러한 한계점들 때문에 자신의 서비스가 JWT를 사용하기 적합한지 충분한 고려가 필요하다.

 

 

참고자료:

https://www.youtube.com/watch?v=1QiOXWEbqYQ

https://jwt.io/

https://dev.to/thecodearcher/what-really-is-the-difference-between-session-and-token-based-authentication-2o39

http://www.opennaru.com/opennaru-blog/jwt-json-web-token-with-microservice/

'' 카테고리의 다른 글

HTTPS와 SSL인증서 & 대칭키, 공개키(비대칭키)  (0) 2021.12.28
robots.txt과 Sitemap  (0) 2021.08.14