JWT(Jason Web Token)란?
- 1) 토큰이란?
- 1-1) 일련의 문자열을 구분할 수 있는 단위이자, 시스템에서 보안 객체의 접근 관리에 사용되는 객체 또는 장치다.
- 1-2) 토큰은 크게 접근(access) 토큰, 보안(security) 토큰, 세션(session) 토큰 등으로 분류할 수 있다.
- 1-3) 접근 토큰(access token)이 가장 많이 사용되는 토큰 형식으로 시스템이나 소프트웨어에서 어떤 특정한 기능이나 데이터에 접근하는 대상에게 권한을 부여하는 데 사용된다.
- 2) JWT이란?
- 사용하고 싶은 정보를 객체에 담아서 해시로 들고, 그 해시값도 같은 객체에 담은 것.
- 즉, 필요한 정보를 자체적으로 지니고 있어서 자가수용적인 특성을 가졌다.
- 자가수용적이므로 두 개체에서 전달되기 쉽다. http 헤더에 넣거나 ,url의 파라미터로 전달이 가능하다.
- jwt에서 발급된 토큰은 토큰에 대한 기본정보, 전달할 정보, 토큰이 검정되었다는것을 증명할 서명을 포함하고있다.
- 3) jwt의 사용
- 회원인증
- 유저가 로그인을 하면 서버가 유저의 정보에 기반한 토큰을 발급해 유저에게 전달한다.
- 유저는 서버에 요청을 할때마다 jwt를 포함해 전달한다.
- 서버는 요청을 받을때마다 토큰이 유효하고 인증되었는지 검증 후, 유저가 요청한 작업에 권한이 있는지 확인하고 작업을 처리한다.
- 서버측에서 유저의 세션을 유지할 필요가 없는 장점. 새션관리 필요없음.
- 유저가 요청했을때 토큰만 확인하면 됨. 자원 아낄 수 있음.
- 정보 교류
- 정보가 서명이 되어있어서 보낸이가 바뀌지는 않았는지, 정보 조작유무를 검증할 수 있따.
- 회원인증
JWT 형태
1) header : 암호화할때 사용할 알고리즘, 사용할 타입
2) payload : 쿠키에 저장할 내용이기 때문에 민감한 정보는 저장하지 않는다. 예) 아이디나 이름 정도
3) signature : (header+payload)를 해시로 만든 값
JWT 만드는 법
1. header와 payload를 먼저 만든다
header : 서명을 위해 어느 알고리즘을 선택할지, 어느 타입을 취할지 결정한다.
payload : 토큰의 목적에 따라 정보를 저장한다.
const header = {
alg : 'sha256', //너 알고리즘이 뭐야
tpy : 'JWT' //너 타입이 뭐야
}
const payload = {
userid:'web7722',
name:'ingoo'
}
2. header와 payload를 인코딩한다.
2-1) header가 인코딩되는 과정 : header를 쿠키에 넣기 위해 string 형태로 바꾸는 것
const header = {alg:'sha256', tpy:'JMT'}
const encodingHeader = JSON.stringify(header)
const encodingHeader1 = Buffer.from(JSON.stringify(header)).toString('base64')
console.log(header)
console.log(encodingHeader)//객체를 string으로 만듬
console.log(encodingHeader1)//길이를 줄이기 위해 64bit로 만듬
//출력값
//{ alg:'sha256', tpy: 'JWT' }
//{ "alg":"sha256", "tpy":"JWT"}
//eyJhbGciOiJzaGEyNTYiLCJ0cHkiOiJKV1QifQ==
2-2) payload가 인코딩되는 과정
2-2-1) header 객채 > string > buffer 16진수 변환시킴 > 너무 길어서 64진수 변환시킴
const encodingPayload = Buffer.from(JSON.stringify(payload)).toString('base64')
console.log(encodingHeader,encodingPayload)
//eyJhbGciOiJzaGEyNTYiLCJ0cHkiOiJKV1QifQ== //헤더
//eyJ1c2VyaWQiOiJ3ZWI3NzIyIiwibmFtZSI6ImluZ29vIn0= //페이로드
const encodingHeader = Buffer.from(JSON.stringify(header))
.toString('base64')
.replace(/[=]/g,'')
//결과 eyJhbGciOiJzaGEyNTYiLCJ0cHkiOiJKV1QifQ
const encodingPayload = Buffer.from(JSON.stringify(payload))
.toString('base64')
.replace(/[=]/g,'')
console.log(encodingHeader,encodingPayload)
//결과 eyJ1c2VyaWQiOiJ3ZWI3NzIyIiwibmFtZSI6ImluZ29vIn0
2-3) 알아두면 좋은 디코딩하는 법
const decodingHeader = Buffer.from(encodingHeader,'base64').toString()
console.log(decodingHeader)
console.log(JSON.parse(decodingHeader))//객체화
//결과
//{"alg":"sha256","tpy":"JWT"} //텍스트
//{ alg: 'sha256', tpy: 'JWT' } //객체화
3. signature 생성
signature : 토큰을 안전하게 확인하는 과정. base64을 통해 헤더와 페이로드를 인코딩해 구분자 .을 이용해 연결시켜 계산한다.
3-1) header와 payload의 해시값을 합쳐서 signature 해시값 생성
const salt = 'web7722'
const signature = crypto.createHmac('sha256',Buffer.from(salt)) //알고리즘,16진수값
.update(`${encodingHeader},${encodingPayload}`) //내용넣기
.digest('base64') //16비트보다 짧게 쓰기 위해 64비트로 반환
.replace(/[=]/g,'') //=제거
console.log('signature : ',signature)
//결과
//signature : UihUkDogPglZlf1ENGM73TnouXgs3JzRvHi2Wh4xhy8
3-2) 만든 signature의 해시값을( = const jwt) 쿠키에 넣음.
jwt를 몰랐을때는 로그인 여부를 확인하려면 세션 생성 여부를 체크해야했다.
그리고 쿠키값이 유효하냐, 유효하지 않냐-를 묻는 미들웨어를 줘야했다.
쿠키는 누구나 임의로 만들 수 있으니까.
jwt는 쿠키값이 토큰으로 바뀐것뿐. 토큰이 정확하냐 안하냐-를 검증한다.
const jwt = `${encodingHeader}.${encodingPayload}.${signature}`//해시값
const cookie={ token : jwt } //쿠키에 넣기
const jwt_arr = cookie.token.split('.') //.을 제외하고 배열로 반환한다
해시값을 잘 만들었는지 확인하려면 JWT 홈페이지(https://jwt.io/ )에 가서 만든 해시값을 넣어서 체크가 가능하다.
3-3) 쿠키값에 잘 들어갔나 확인
const cookie={ token : jwt } //쿠키에 넣기
const [head,pay,sign] = cookie.token.split('.') //구분하기
const designature = crypto.createHmac('sha256',Buffer.from('web7722'))
.update(`${head},${pay}`)
.digest('base64')
.replace(/[=]/g,'')
console.log(designature === sign)
//결과 true
3-4) 쿠키로 가져온 header, payload, signature가 정확한건지 확인하기
header, payload의 해시값을 새로 만들어서 쿠키로 가져온 header, payload 해시값과 비교한다.
const [head,pay,sign] = cookie.token.split('.')
const decodingHeader = Buffer.from(head,'base64').toString()
console.log(header, decodingHeader)
//결과
//암호화되어있는 header의 해시값
//{"alg":"sha256","tpy":"JWT"} 텍스트 형태로 되어있는 JSON
//JSON.parse()를 썼으면 객체형태로 나왔을 것
const decodingPayload = JSON.parse(Buffer.from(pay,'base64')).toString()
console.log(payload,decodingPayload)
//결과
//암호화되어있는 payload의 해시값
//{ userid:'web7722', name:'ingoo'}
const designature = crypto.createHmac('sha256',Buffer.from('web7722'))
.update(`${head},${pay}`)
.digest('base64')
.replace(/[=]/g,'')
console.log(designature===sign)
//결과
//true
단순히 쿠키/세션을 가진것보다 보안성이 높은이유
signature가 헤더와 페이를 합친 후 자신만의 규칙(salt)를 적용해 만들어지는 해시값을 가지고 있기 때문이다.
헤더나 페이의 내용이 조금만 바뀌어도 해시값이 완전히 달라지게 하는 salt의 특성덕분에
클라이언트가 쿠키 내용을 바꿔서 접속할 수가 없다.
즉, salt값을 모르면 수정할 수 없다.
서버가 바뀐 쿠키의 내용과 기존에 갖고있던 해시값을 각각 해시화 해서 비교해 다르면 거부하기 때문이다.
이 방식의 장점
- 1) 데이터의 주체가 클라이언트기 때문에 사용자가 로그인을 많이해도 서버에게는 부담이 없다.
- (서버가 터질 일이 없다.)
- 2) 클라이언트가 이미 갖고있어서 db.query를 날리지 않아도 된다.
- 3) 사용자가 정보를 가지고 있기때문에 서버가 달라도 로그인이 유지될 수 있다.
알고리즘의 취약성
보안 상담가 팀 메클레인(Tim McLean)은 alg 필드를 사용하여 토큰의 유효성을 잘못 확인하는 일부 JWT 라이브러리의 취약성을 보고하였다. 이 취약점들이 패치가 되었으나 메클레인은 비슷한 구현체 혼동을 예방하기 위해 alg 필드를 구식 처리하는 것을 제안하였다.
취약성 보완법
- JWT 헤더만으로 유효성을 확인하지 않을 것
- 알고리즘을 인지할 것
- 적절한 키 크기를 사용할 것
'프로그래밍 > database' 카테고리의 다른 글
[220304] AJAX를 응용해서 입력값 받아오기 (0) | 2022.03.04 |
---|---|
[220303] npm과 jwt을 활용해서 로그인 확인 기능 만들기 (0) | 2022.03.03 |
[220302] 암호화 (buffer, hash, salt) (0) | 2022.03.02 |
[220221] sql 오류모음, 피드백 (0) | 2022.02.22 |
[220216] DB와 서버를 연결해주는 Connection Pool (0) | 2022.02.21 |
댓글