JWT를 통한 로그인

sarah·2022년 12월 21일
0

로그인

목록 보기
2/2
💡 session에 로그인 정보를 저장하기엔 사용자가 늘어남에 따라 서버의 과부화와 다중 서버 사용으로 인한 별도의 처리를 해줘야 하기 때문에 이를 해결할 방법으로 JWT를 이용한 로그인 방식을 사용해보기로 하였다.

JWT란?

JWT(JSON Web Token)은 당사자 간 정보를 안전하게 전송할 수 있는 방법을 JSON 객체로 정의한 개방형 표준(RFC 7519)이다.
이 정보는 디지털 서명을 사용하기 때문에 데이터 위/변조를 검증할 수 있으며, 신뢰할 수 있다.
JWT는 (HMAC 알고리즘으로) 비밀 또는 RSA 또는 ECDSA를 사용하는 공용키/비밀키 쌍을 사용하여 서명할 수 있다.
서명된 토큰은 그 안에 포함된 클레임의 무결성을 검증할 수 있는 반면, 암호화된 토큰은 그러한 클레임을 다른 당사자로부터 숨길 수 있다.
토큰이 공용/비밀 키 쌍을 사용하여 서명될 때, 서명은 또한 개인 키를 가진 당사자만이 서명한 당사자임을 인증할 수 있다.

로그인 방식

  1. 먼저 브라우저에서 Login요청을 한다.
  2. 서버에서 JWT를 발급한다.
  3. 발급한 JWT를 브라우저로 보낸다.
    (이때 JWT를 response body에 보낼지 cookie에 담아서 보낼지는 선택사항.
    중요한건 브라우저에서 JWT를 어디에 저장하냐는 것이다.)
  4. 브라우저에서 JWT를 저장할 곳(LocalStorage& cookie)을 선택한다.
    (아래에서 각각의 차이를 설명하겠음.)
  5. 이후 브라우저에서 요청시 발급받은 JWT를 함께 보낸다.
  6. 서버에서 JWT에 포함된 Signature를 확인 후 user정보를 확인한다.
  7. 브라우저로 응답을 보낸다.

JWT 구조

  • 세 가지 파트로 나뉘어져 있다.

  • typ: 토큰의 타입을 지정. 바로 JWT를 말하는 것.
  • alg: 해싱 알고리즘을 지정. 보통 HMAC-SHA256 혹은 RSA 가 사용되며, 이 알고리즘은 토큰을 검증 할 때 사용되는 signature 부분에서 사용된다.

payload

  • 토큰의 내용물이 인코딩된 부분
  • 여기에 담는 정보의 한 ‘조각’ 을 클레임(Claim) 이라고 부르고, 이는 Json(Key/Value) 형태의 한 쌍으로 이뤄져있다.
  • 토큰에는 여러개의 클레임들을 넣을 수 있습니다.

signature

  • 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드입니다.
  • 헤더(Header)와 내용(Payload)의 값을 각각 BASE64로 인코딩하고, 인코딩한 값을 비밀키를 이용해 헤더에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64로 인코딩하여 생성한다.
  • secret key로 사용되는 일련의 문자열이라고 이해하면 쉽다.
  • 토큰이 제 3자에 의해 탈취되어서 페이로드의 내용이 변경된다면 토큰의 값이 크게 변경

Access Token & Refresh Token

Refresh Token이 필요한 이유

Access Token 만을 통한 인증 방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점이다.

Access Token은 발급된 이후, 서버에 저장되지 않고 토큰 자체로 검증을 하며 사용자 권한을 인증하기 떄문에, Access Token이 탈취되면 토큰이 만료되기 전 까지, 토큰을 획득한 사람은 누구나 권한 접근이 가능해 지기 때문이다.

JWT는 발급한 후 삭제가 불가능하기 때문에, 접근에 관여하는 토큰에 유효시간을 부여하는 식으로 탈취 문제에 대해 대응을 하여야 한다.

이처럼 토큰 유효기간을 짧게하면 토큰 남용을 방지하는 것이 해결책이 될 수 있지만, 유효기간이 짧은 Token의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 Token을 발급받아야 하므로 불편하다는 단점이 있다. 그렇다고 무턱대고 유효기간을 늘리자면, 토큰을 탈취당했을 때 보안에 더 취약해지게 된다.

이때 “그러면 유효기간을 짧게 하면서  좋은 방법이 있지는 않을까?”라는 질문의 답이 바로 Refresh Token이다.

이름이 다르지만 형태 자체는 Refresh Token은 Access Token과 똑같은 JWT다.

단지 Access Token은 접근에 관여하는 토큰이고, Refresh Token은 재발급에 관여하는 토큰 이므로 행하는 역할이 다르다고 보면 된다.

예를 들면 처음에 로그인을 했을 때, 서버는 로그인을 성공시키면서 클라이언트에게 Access Token과 Refresh Token을 동시에 발급한다.

서버는 데이터베이스에 Refresh Token을 저장하고, 클라이언트는 Access Token과 Refresh Token을 쿠키, 세션 혹은 웹스토리지에 저장하고 요청이 있을때마다 이 둘을 헤더에 담아서 보낸다.

이 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 재발급해주는 열쇠가 된다.

따라서 만일 만료된 Access Token을 서버에 보내면, 서버는 같이 보내진 Refresh Token을  DB에 있는 것과 비교해서 일치하면 다시 Access Token을 재발급하는 간단한 원리이다.

그리고 사용자가 로그아웃을 하면 저장소에서 Refresh Token을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장한다.

Access / Refresh Token 재발급 원리

1. 기본적으로 로그인 같은 과정을 하면 Access Token과 Refresh Token을 모두 발급한다.

⇒ 이때, Refresh Token만 서버측의 DB에 저장하며, Refresh Token과 Access Token을 쿠키 혹은 웹스토리지에 저장한다.

2. 사용자가 인증이 필요한 API에 접근하고자 하면, 가장 먼저 토큰을 검사한다.

이때, 토큰을 검사함과 동시에 각 경우에 대해서 토큰의 유효기간을 확인하여 재발급 여부를 결정한다.

  • case1 : access token과 refresh token 모두가 만료된 경우  에러 발생 (재 로그인하여 둘다 새로 발급)
  • case2 : access token은 만료됐지만, refresh token은 유효한 경우  refresh token을 검증하여 access token 재발급
  • case3 : access token은 유효하지만, refresh token은 만료된 경우  access token을 검증하여 refresh token 재발급
  • case4 : access token과 refresh token 모두가 유효한 경우  정상 처리

보안 이슈

서버로 부터 받은 JWT 토근을 클라이언트가 어디에(LocalStorage&Cookie) 저장할지에 대해 알아보기 전에, 먼저 발생할 수 있는 보안 이슈에 대해 먼저 알아보겠다.

XSS 공격

  • XSS 공격 공격자(해커)가 클라이언트 브라우저에 Javascript를 삽입해 실행하는 공격이다.
  • Javascript를 통해 사이트의 글로벌 변수값을 가져오거나 그 값을 이용해 사이트인 척 API 콜을 요청할 수도 있다. 다시 말하면 공격자의 코드가 내 사이트의 로직인 척 행동할 수 있는 것이다.

CSRF 공격

  • 공격자가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격
  • API 콜을 요청할 수 있는 클라이언트 도메인이 누구인지 서버에서 통제하고 있지 않다면 CSRF가 가능하다.(IP 허용)
  • 공격자가 클라이언트에 저장된 유저 인증정보를 서버에 보낼 수 있다면, 제대로 로그인한 것처럼 유저의 정보를 변경하거나 유저만 가능한 액션들을 수행할 수 있다.
  • 정상적인 Request를 가로채 피해자인 척하고 백엔드 서버에 변조된 Request를 보내 악의적인 동작을 수행하는 공격을 의미힌다.(피해자 정보 수정, 정보 열람)

클라이언트단 JWT 저장소

LocalStorage

  • (장점) CSRF 공격에는 안전하다.
    그 이유는 자동으로 Request에 담기는 쿠키와는 다르게 JS코드에 의해서 헤더에 담기므로 XSS를 뚫지 않는 이상 공격자가 정상적인 사용자인 척 Request를 보내기는 어렵다.
  • (단점) XSS에 취약하다.
    공격자가 localStorage에 접근하는 JS코드 한 줄만 주입하면 localStorage를 공격자가 내 집처럼 드나들 수 있다.
  • (장점) XSS 공격으로부터 localStorage에 비해 안전하다.
    쿠키의 httpOnly 옵션을 사용하면 JS에서 쿠키에 접근 자체가 불가능하다.
    그래서 XSS 공격으로 쿠키 정보를 탈취할 수 없다.(httpOnly 옵션은 서버에서 설정 가능)
  • 하지만 XSS 공격으로부터 완전히 안전한 것은 아니다.
    httpOnly 옵션으로 쿠키의 내용을 볼 수 없다 해도 JS로 Request를 보낼 수 있으므로 자동으로 Request에 실리는 쿠키의 특성 상 사용자의 컴퓨터에 요청을 위조할 수 있기 때문이다.
    공격자가 귀찮을 뿐이지 XSS가 뚫린다면 httpOnly cookie도 안전하진 않다.
  • (단점) CSRF 공격에 취약하다.
    자동으로 HTTP Request에 담아서 보내기 때문에,
    공격자가 Requset url만 안다면 사용자 관련 link를 클릭하도록 유도하여 Request를 위조하기 쉽다.

어디에 저장해야 하는가?

refreshToken만을 secure httpOnly 쿠키에 저장해 CSRF 공격을 방어할 것이다. 
accessToken 은 웹 어플리케이션 내 로컬 변수에 저장해 사용하며, API를 요청할 때 Authorization 헤더에 넣어 보내준다. XSS 취약점을 이용한 API 요청 공격은 클라이언트와 서버에서 추가적으로 방어 해야 한다.

JWT 장점

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

JWT 단점

  1. Self-contained : 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다.
  2. 토큰 길이 : 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
  3. Payload 인코딩 : payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에, 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로, payload에 중요 데이터를 넣지 않아야 한다.
  4. Store Token : stateless 특징을 가지기 때문에, 토큰은 클라이언트 측에서 관리하고 저장한다. 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다.

결론

jwt를 localStorage, cookie 중 어디에 저장할 지는 더 자세히 공부해보고 공격에 대비해 봐야겠다. 다음 글에선 코드로 jwt를 적용한 예시를 정리해보겠다.

[출처]

[인증/인가]Session(세션)과 Token(토큰)(JWT)의 차이점

[WEB] 📚 Access Token & Refresh Token 원리 (feat. JWT)

[WEB] 📚 JWT 토큰 인증 이란? - 💯 이해하기 쉽게 정리

0개의 댓글