[Spring Security] JWT(Json Web Token) 개념

세션+쿠키 로그인

 

 일반적으로 웹에서 서버로 url을 통해 요청하면, 서버에서는 적절한 컨트롤러가 매핑하고 내부 로직등을 통과한 후 뷰(html 등)를 리턴해준다. 이 때 (세션 만료 후)최초 요청시 헤더 쿠키를 이용하여 세션 id를 발급한 후 사용자 정보도 같이 저장하고, 이후 요청마다 서버의 세션 저장소에서 쿠키의 세션id를 확인하여 자동으로 사용자 인증과 함께 세션 만료 여부도 확인한다.

(https://eckrin.tistory.com/entry/Spring-MVC-%EC%BF%A0%ED%82%A4%EC%84%B8%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8)

 

 

JWT?

 그렇다면 왜 세션 로그인 방식이 아닌, Json Token를 이용한 로그인을 사용할까?

 세션을 이용한 로그인의 경우 토큰 방식보다 보안에는 강하다는 장점이 있지만, 서버에서 세션 저장소에 만료되지 않은 세션id를 저장하고 있어야 하기 때문에 클라이언트의 수가 많은 대형 서비스에서는 메모리에 부하가 걸리게 된다. 이를 위해서 서버 분산이나 데이터서버와 같은 방법을 이용하기도 하지만, 이 역시 별도의 서버를 필요로 하고 확장이 어렵게 만든다.

 

 JWT는 이름 뜻 그대로, 세션에 사용자를 저장하는 세션 방식과 다르게 JSON객체에 사용자 정보를 담으며, 구분자(.)을 기준으로 header, payload, signature 3가지 구조로 분류된다.

1. Header

alg와 typ은 정보를 암호화할 해싱 알고리즘과 토큰의 타입을 말한다.

 

2. Payload

payload는 토큰에 들어갈 정보(클라이언트의 고유 id, 토큰 유효기간)들이 들어가는 영역이다.

 

3. Signature

signature는 인코딩된 header와 payload를 더한 뒤 서버의 private key로 서명하여 생성하므로, 확인하고 신뢰할 수 있다.

 

//base64, hs256는 암호화 로직
sig = HS256(BASE64(header)+"."+BASE64(payload)+"."+private key)
jwt토큰 = BASE64(header).BASE64(payload).BASE64(sig) 

 

 

 

출처 https://jwt.io/introduction

 

>>> private key로 암호화하면 공개되어있는 public key로 복호화 가능하므로 위험한 것 아닌가?

private key로 암호화하면 public key를 이용해서 복호화한들 다시 암호화가 불가능하기 때문에 signature 자체를 위조하는것은 불가능하고, public key로 암호화하면 private key를 알 수 없으므로 복호화 자체가 불가능하다. 사실 어떤 방법을 사용하든지에 상관 없이 signature를 수정하게 되면 위조 여부를 알 수 있긴 하다. 그런데 JWT의 목적은 private key로 서명하여 위조 여부를 verify하는 것이 목적이므로, 일반적으로 private key를 사용하여 암호화(서명)한다. (https://eckrin.tistory.com/entry/Network-Transport-Layer-Security-TLS를 보면 서명과 암호화의 차이가 나온다.)

 

header와 payload는 단순 인코딩된 값이므로 제 3자가 복호화하고 조작 가능하지만, signature는 서버의 비밀 키가 없는 한 복호화 혹은 재 암호화할 수 없으므로 서버에서 토큰 위변조 여부를 확인할 수 있다.

그렇기에 서버에서 별도의 공간을 확보하여 클라이언트의 정보를 저장할 필요가 없고, 서버가 토큰을 생성하여 클라이언트에게 보내주면 클라이언트가 토큰을 보관하고 있다가 요청 시 헤더에 토큰을 실어보내면 된다,

 

 

 

 

JWT 인증 방법

  1.  클라이언트 로그인 요청이 들어오면, 서버는 검증 후 클라이언트 ID등의 고유 정보를 payload에 담는다.
  2. (header+payload)를 서버의 private key로 암호화하여 서명 후 Access Token을 발급한다.
  3.  클라이언트는 전달받은 토큰을 저장해두고, 서버에 요청할때마다 토큰을 요청 헤더에 포함하여 함께 전달한다.
  4.  서버는 토큰의 signature을 서버의 private key로 복호화한 후 위변조 여부나 유효기간 등을 확인한다.
    ([header+payload]가 signature를 복호화한 것과 동일한지 확인한다.)
  5.  유효한 토큰일 경우 요청에 응답한다.

 

 

장점

  1. Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있습니다.
  2. 인증 정보에 대한 별도의 저장소가 필요없습니다.
  3. JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있습니다.
  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태가 됩니다.
  5. 확장성이 우수합니다.
  6. 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능합니다.
  7. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있습니다.
  8. 모바일 어플리케이션 환경에서도 잘 동작합니다.

단점

  1. 쿠키/세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해집니다.
  2. Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없습니다.
  3. 토큰을 탈취당하면 대처하기 어렵습니다.
  4. 토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문입니다.
  5. 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제할 수 있습니다.

 

 

 

해결책 - refreshToken 사용

 다만 순수한 JWT는 통신 중 access Token을 탈취당했을 경우에 토큰의 만료 기한을 짧게 설정하는 것 말고는 대응 방법이 없으므로, 클라이언트가 로그인 요청을 보내면 서버는 accessToken과 그보다 긴 만료 기간을 가진 refreshToken이라는 2개의 토큰을 발급한다. 이 refreshToken은 서버(DB)도에 저장되어, accessToken이 만료될 경우 새로운 accessToken을 생성할 때 사용된다.

  1. 클라이언트 회원가입 요청이 들어오면, 서버는 회원가입 처리(DB 등록)하고 accessToken과 refreshToken 발급
  2. 서버는 발급한 accessToken과 refreshToken을 클라이언트에 전송하고, 클라이언트는 두 토큰을 저장
  3. 이후 클라이언트는 데이터 요청시마다 accessToken을 보내고, 서버는 사용자로부터 온 토큰이 유효한지만 확인
  4. 그러다가 클라이언트의 accessToken 만료
  5. 클라이언트가 만료된 accessToken으로 요청하면, 서버는 refreshToken을 보내라고 요청
  6. 클라이언트가 refreshToken을 보내면 서버는 받아서 유효성 검사
  7. 유효한 refreshToken이라면 accessToken 재발급
  8. 그러다가 refreshToken 만료시 재인증 요구 후 accessToken과 refreshToken 발급

 

클라이언트는 accessToken이 만료되었을 때 refreshToken을 사용하여 accessToken의 재발급을 요청하게 하여, accessToken의 만료 기한을 짧게 설정하지만 사용자는 자주 로그인할 필요가 없게 만들 수 있으며, 서버가 강제로 refreshToken을 만료시키면 accessToken은 갱신할 수 없게 된다.

 

 

 또한, 만약 accessToken을 갱신하기 위해서 전송한 refreshToken이 탈취당한다면, 해커는 그 refreshToken을 이용하여 새로운 accessToken을 생성한 후 서버에 접근할 수 있게 된다. 따라서 서버는 refreshToken과 accessToken 쌍의 정보를 저장하여 동일한 refreshToken에 대한 accessToken의 개수가 여러개가 된다면 refreshToken이 탈취당했다고 가정하고, 두 개의 accessToken을 모두 만료시킨다.

 

 

 

이와 같이 refreshToken을 사용할 경우 장점이 존재하지만, 순수한 jwt와는 다르게 검증을 위해 서버와 클라이언트 모두 refreshToken을 별도의 보안이 유지되는 공간에 저장해야 하고, 이 과정이 메모리를 차지하기 때문에 순수하게 accessToken만을 이용하는 방법보다 성능의 저하가 발생한다는 단점은 해결할 수 없다.

 

 

참고)

https://velog.io/@park2348190/JWT%EC%97%90%EC%84%9C-Refresh-Token%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Access-Token-Refresh-Token-%EC%9B%90%EB%A6%AC-feat-JWT