JWT : JSON WEB TOKEN
전자 서명 된 URL로 이용할 수 있는 문자만으로 구성된 JSON
전자 서명은 JSON의 변조를 체크 할 수 있음
인증방식 및 원리
JWT 구성
aaaa.bbbb.cccc 와 같이 세 파트로 나누어짐
각각 header.payload.signature 로 구성됨
Base64 인코딩의 경우 "+", "/", "=" 이 포함되지만 JWT는 URI에서 파라미터로 사용할 수 있도록 URL-Safe한 Base64url 인코딩을 사용(+, /, = 같은 문자가 다른 string으로 대체 되는 듯)
- 토큰의 타입과 해시 암호화 알고리즘으로 구성
- 토큰 유형(JWT)
- 해시 알고리즘(HMAC, SHA256, RSA 등)
{
"typ": "JWT",
"alg": "HS256"
}
Payload
전달하려는 정보(ex. 유저 id, role 등) : claim 이라고도 부름
{
"iss": "//sso.tvcf.co.kr",
"iat": 1626308465,
"exp": 1626394865,
"roles": [
"guest",
"user"
],
"userId": "tonydev",
"userName": "tony",
"userType": 3,
"userLevel": 1
}
일반토큰 vs claim
-
일반 토큰의 문제점
- 발급된 토큰에 대해 만료를 시킬 수단이 없다.
- 발급된 토큰을 검사하거나 처리할 때 마다 DB에 접근하여 검사할 경우 부담이 생긴다.
- 사용자 로그아웃 등으로 인한 토큰을 관리할 수 있는 방법이 없다.
-
클레임(claim : 청구, 요구)
- 정보를 담고 있는 토큰
- 클레임의 종류 3가지
- 등록된 클레임(Registered Claim)
- 토큰 정보를 표현하기 위해 이미 정해진 데이터 종류, 선택적으로 작성 가능
- iss : 토큰 발급자(issuer)
- sub : 토큰 제목(subject)
- aud : 토큰 대상자 (audience)
- exp : 토큰의 만료 시간(expiration)
- nbf : 토큰 활성 날짜(not before) - 이 날짜가 지나기 전 토큰은 활성화 되지 않음
- iat : 토큰이 발급된 시간(issued at)
- jti : JWT Id - 중복 방지를 위해 사용, 일회용 토큰(Access token 등)에 사용
- 시간 관련 숫자 : NumericData 확인 방법
- new Date(parseInt("1626394865") * 1000)
- 공개 클레임(Public Claim)
- 비공개 클레임(Public Claim)
- 실제 사용을하는 개발자가 지정, 서버와 클라이언트가 서로 정의하여 사용하는 클레임
- { "token_type": "access" }
signature
secret key를 포함하여 암호화 되어 있음
- header에 지정된 알고리즘과 secret key, 서명으로 payload와 header를 담는다.
Access token과 Refresh token
Access token : JWT
Refresh token : access token을 얻기 위한 토큰
- access 토큰을 사용하다가 만료되면 refresh 토큰으로 재발급
- 평소 API요청엔 refresh token을 같이 보내지 않음
- refresh 토큰은 어디에서 저장되고 언제 같이 보내지는 걸까?
- 평소엔 access 토큰만 api서버로 같이 전달해서 인증하는데 refresh 토큰은 브라우저의 어디에 저장는건지 ?
- access 토큰이 만료된 시점을 어떻게 알고 refresh 해주는 건지 어떻게 ?
- refresh 토큰이 저장될 만한 곳
- Client side(browser)
1-1. Local storage
- 자바스크립트로 접근이 너무 쉬워서 XSS 공격에 취약
1-2. Cookie
- 쿠키의 경우 HTTPOnly와 Secure옵션을 사용하고 CSRF 공격에 대비를 하면 어느정도 보안이 됨
- Server side
2-1. 세션
- 세션에 저장하고 세션 만료주기를 길게 준다 : 사용자가 많아지면 말도 안되는 방식, 애초에 세션을 사용하지 않으려고 만든게 엑세스 토큰방식임
2-2. DB에 저장
- DB에 refresh 토큰을 저장하고 그 index값을 쿠키나 로컬스토리지에 저장(해쉬처리 하면 보안에 더 유리)
JWT 발행
new Buffer('{"alg":"HS256","typ":"JWT"}').toString("base64")
payload(JWT Claim Set)
new Buffer('{"iss":"John Doe","exp":1434290400000,"username":"john","age":25,"iat":1434286842654}').toString("base64")
signature
var crypto = require('crypto');
var secretKey = 'secret';
var headerAndClaimSet = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJKb2huIERvZSIsImV4cCI6MTQzNDI5MDQwMDAwMCwidXNlcm5hbWUiOiJqb2huIiwiYWdlIjoyNSwiaWF0IjoxNDM0Mjg2ODQyNjU0fQ';
crypto.createHmac('sha256', secretKey).update(headerAndClaimSet).digest('base64')
JWT의 특징
- 토큰 자체가 데이터를 가지고 있음
- 누구나 열어볼 수 있기 때문에 중요한 정보는 넣으면 안됨
- 다른 토큰보다 길이가 길다
- 토큰을 강제로 만료시킬 방법이 없다.
- 뱅킹서비스, 주신관련 서비스 등 실시간 보안이 중요한 곳은 JWT를 쓰기보다 DB에 부담을 주더라도 DB에 토큰을 넣는 방식을 선택하는 게 나을 수 있음
- 보안보다 사용성이 중요한 어플리케이션에선 정보를 넣을 수 있는게 큰 장점이 될 수 있음
- 만료기간이 짧은 access 토큰과 달리 refresh 토큰은 만료기간이 매우 김
토큰 확인 절차
case1: access token과 refresh token 모두가 만료된 경우 -> 에러 발생
case2: access token은 만료됐지만, refresh token은 유효한 경우 -> access token 재발급
case3: access token은 유효하지만, refresh token은 만료된 경우 -> refresh token 재발급
case4: accesss token과 refresh token 모두가 유효한 경우 -> 다음 미들웨어로
세션-쿠키 방식과 비교
세션-쿠키 인증 방식
세션-쿠키 인증방식은 로그인에서 아직까지 제일 많이 쓰이는 인증방식
쿠키(Cookie)
- 클라이언트(브라우저)의 저장소
- 브라우저에 300개 까지 저장가능, 도메인당 20개 까지, 개당 4kB까지 저장
- 구성 요소
- Name-value
- 유효기간(MaxAge or Expires)
- 도메인(Domain) : 쿠키를 전송할 도메인
- 경로(Path) : 서버가 라우팅 할 때 사용하는 경로, default : "/"
- 서버에서 Path 하위 모든 route에 쿠키가 전송 됨
- Secure : true로 설정된 경우 HTTPS인 경우에만 쿠키를 전송
- HttpOnly : 자바스크립트에서 브라우저의 쿠키에 접근 여부를 결정
- true : js에서 쿠키접근 불가능
- false(default) : XSS 공격에 취약
- SameSite : Cross-Origin 요청을 받은 경우, 쿠키 전송 여부 결정
- Lax : Cross-origin 요청 시 'GET' 메소드에 대해서만 쿠키 전송
- Stric : Same-site 인 경우에만 쿠키 전송
- None : 항상 쿠키 전송 가능하지만 Secure 옵션 활성화 필요
세션(서버의 저장소)
- 서버쪽 쿠키, key-value로 이루어진 객체
- 서버에선 클라이언트 구분을 위해 세션 ID를 부여
- 클라이언트가 request를 보내면 서버가 헤더에 쿠키를 담아 보낼 때 세션ID를 보냄
- 보안면에서 쿠키보다 우수
- 사용자가 많아지면 서버메모리를 많이 차지하게 됨
세션과 쿠키의 차이
- 비슷 함, 결국 세션도 쿠키를 이용하여 서버에 저장 하는 것
- 브라우저 종료를 세션에 알려주려면 beforeunload 이벤트를 이용 할 수 있음
토큰방식(JWT)과 세션-쿠키 방식의 차이
- 세션쿠키 방식은 세션ID만 보냄
- 세션이 서버의 메모리를 필요로 함
적용했던 프로젝트 복습
jasonwebtoken module을 사용(npm i jsonwebtoken)
jasonwebtoken에선 access token과 refresh 토큰을 어떻게 관리할까?
// tvcf에 적용된 방식 분석
tvcf에 적용된 방식 분석
tvcf의 토큰 만료기간은 1일이다.
refresh 토큰은 어디에 보관되는가?
- 로컬스토리지엔 access token만 보인다.
- cookie엔 로그인 전후에 변화가 없는 것으로 보아 서버사이드에 보관되는 것으로 추정된다.
- 세션이나 DB 둘중에 한 곳에 보관 될 것 같다.(DB에 보관될 것으로 추정)
내가 적용한 JWT(npm jsonwebtoken)와 tvcf에서 적용한 방식 차이 분석
{
"id": 9,
"iat": 1626355500
}
- 나는 refresh 토큰을 발행하지 않고 로컬 스토리지에 access 토큰을 영구저장했었음
토큰을 통해 전송 되는 것은 user.id 하나만 전송했었음
- refresh 토큰 개념이 없었고 apollo client의 캐싱 기능을 사용해서 생각보다 jwt방식이지만 토큰의 페이로드에 많은 정보를 담을 필요가 없었던 것 같다.
결론
JWT 인증 구현 방식은 많다.
어느것을 정답이라고 말하긴 어렵다.
나는 미들웨어로 클라이언트에서 요청을 받을 때마다 verify 후 보냈었는데 회사의 서버에선 어떻게 로직이 구성되어 있는지 궁금하다.
참고 문헌