오늘은 JWT(Json Web Token)와 세션관리에 대해서 정리보겠습니다.
1. JSON Web Token(JWT)
JWT는 토큰 정보와, 해당 토큰이 유효함을 증명할 수 있는 signature가 포함되어 있는 토큰인데요.
JWT는 Header, Payload, 그리고 Signature로 구성되어 있습니다.
1-1. Header
Header에서는 토큰의 유형과 사용된 서명 알고리즘을 지정합니다.
비대칭인 'RS256'과 같은 알고리즘을 쓸 수도 있고, 'HS256'을 쓸 수도 있겠지요.
아래에서는 'HS256'알고리즘을 사용하였습니다.
{
"alg": "HS256",
"typ": "JWT"
}
1-2. Payload
Payload에는 토큰이 전달하고자 하는 실제 데이터가 담겨 있습니다.
이 부분에는 토큰 발급 대상자, 만료 시간, 주제 등 다양한 클레임(claims)을 포함할 수 있습니다.
유저에 대한 상태나 정보등을 담을 수 있는데요.
아래와 같은 정보들을 담을 수 있습니다.
{
"sub": "77823323",
"name": "Big Park",
"iat": 1516239122
}
1-3. Signature
Signature는 토큰이 변조되지 않았음을 증명하기 위한 서명으로,
토큰의 Header와 Payload를 서명 알고리즘으로 해시하고,
비밀키 또는 공개키/개인키 쌍을 사용하여 생성합니다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
https://jwt.io/ 에 가면 아래와 같이 실제로 인코딩된 값을 볼 수 있습니다.
이러한 3가지 구조덕분에,
JWT는 보안이 필요한 환경에서 데이터를 안전하게 전송하고,
클라이언트 사이드에서 토큰의 유효성을 검증할 수 있게 해 줍니다.
2. JWT 구현
2-1. JWT라이브러리 설치
JWT라이브러리는 www.jwt.io/를 참고하면 알 수 있는데요.
Nodejs에서는 jsonwebtoken 모듈을 사용할 수 있습니다.
아래 npm명령어로 jsonwebtoken을 설치해 줍니다.
npm install --save jsonwebtoken
해당 패키지에 대한 공식git허브 페이지는 아래와 같습니다.
https://github.com/auth0/node-jsonwebtoken
그럼 이제 본격적으로 JWT를 구현 하겠습니다.
로그인과 로그아웃의 UI 및 컨트롤러가 구현된 상태라고 가정하고,
JWT를 발행하고 검증하는 단계에 대해서 정리해 보도록 하겠습니다.
먼저 jwt모듈을 require 합니다.
const jwt = require('jsonwebtoken');
2-2. 토큰 발행하기
토큰을 발행하기 위해 아래와 같은 형식을 따르는데요.
payload는 객체나 string형태로 모두 전달 가능하구요.
secret은 토큰의 Signature 부분을 생성할 때 사용되는 문자열로,
이 secret 문자열을 사용해 토큰의 유효성을 검증합니다.
중요한 점은, secret이 외부에 노출되어서는 안 된다는 것입니다.
Secret이 클라이언트나 외부에 노출되면,
누군가가 그 secret을 사용하여 유효한 토큰을 위조할 수 있기 때문입니다.
만약 secret이 노출된 경우, 이를 신속하게 변경하고 기존의 토큰들을 무효화시켜야 합니다.
jwt.sign(payload, secret, options, [callback])
아래 코드로 토큰을 발행 할 수 있습니다.
위에서 본 인자 순서대로 값을 넣어 주는 것 인데요.
여기에서 expiresIn는 유효기간인데,
"2 days", "10h", "7d" 같이 시간, 일 등의 단위를 사용할 수 있습니다.
만약 단위를 사용하지 않는다면 ms단위로 처리가 됩니다.
jwt.sign(
{ nickname: 'ladida'},
'secret',
{ expiresIn: '1h' }
);
이렇게 발행된 토큰을 서버에서 HTTP의 Response 헤더에 넣어서 클라이언트에게 전달해 주면 되겠지요.
access token의 경우는 x-access-token (사용자 정의라는 의미에서 붙이는 x를 추가함)과 x-refresh-token과 같은 이름으로 넣어줄 수 있겠습니다.
2-3. 토큰 검증하기
토큰 발행하는 것처럼 토큰을 검증하는 로직도 복잡하지 않습니다.
jwt.verify(token, 'wrong-secret', function(err, decoded) {
if(err) console.log(err);
console.log("decoded payload", decoded);
});
이렇게 해서 토큰을 발행하고 검증하는 것에 대해 알아보았구요.
이러한 토큰을 좀 더 안전하게 사용하는 방법으로서의
액세스 토큰과 리프레쉬 토큰에 대해서 알아보겠습니다.
3. 세션관리와 JWT토큰
위에서 세션관리에 사용할 JWT토큰에 대해서 정리해 보았는데요.
세션관리에 대해서 좀 더 보도록 하겠습니다.
사용자들은 크롬이나 익스플로러 같은 브라우저를 가지고 접속을 하게 되는데요.
접속후에 로그인이라는 과정을 거쳐 해당사이트의 컨텐츠를 사용하거나 특정 요청을 할 수 있습니다.
그러나 계속 로그인을 하게 하는 것은 매우 불편한 일이므로 보통은 사용자의 세션에 그 상태를 저장하는데요.
3-1. 서버에 세션을 저장하는 방법
세션은, 서버의 메모리에 저장하기도 하고, 데이터베이스에 넣을 수도 있습니다.
사용자가 많을수록, 메모리든 데이터베이스든 부하를 받게 되는 부분이 있지만,
언제든 문제가 생기면 무효화 할 수 있으므로 대응이 빠르다고 할 수 있습니다.
3-2. 클라이언트에 세션을 저장하는 방법
세션을 클라이언트에 저장할 경우, 암호화된 JWT토큰을 사용해 저장할 수 있는데요.
유저의 로그인정보를 서버에 담지 않을 수 있다는 점에서 부하를 덜 수 있지만,
혹여 탈취되었을 때의 문제가 있으므로 세심한 주의가 필요합니다.
유저가 로그인인증을 하면 서버에서 사인된 토큰을 발급받구요.
클라이언트에서 이 토큰을 저장한 다음,
헤더값에 토큰값을 전송해서 서버에서 검증받는 방식입니다.
서버에 상태를 저장하지 않으므로,
서버부하를 상당히 줄여준다는 면에서는 아주 좋습니다.
하지만 반대로 서버가 컨트롤 할 수가 없으므로,
클라이언트의 컴퓨터가 탈취당한 경우 유효시간까지는,
강제로 개별 클라이언트에 해당토큰을 무효화 할 방법이 존재하지 않습니다.
유저가 비밀번호를 변경한 경우에도,
이전 비밀번호로 유효했던 토큰에 대해 서버가 개별적으로 무효화 시킬 수 없으므로,
유효기간이 다 되기까지 계속 유효한 상태가 되버릴 수 있구요.
여기에 대한 대안으로, 액세스 토큰과 리프레쉬 토큰을 나누어서 사용하는 방법이 있는데요.
아래에서 보도록 하겠습니다.
4. 액세스 토큰과 리프레쉬 토큰
4-1. 유효기간이 짧은 액세스 토큰과 기간이 긴 리프레쉬 토큰의 발급
매번 사용자가 로그인 인증을 하게 할 수는 없고, 토큰의 유효기간을 길게 주면,
사용자의 컴퓨터가 탈취당할 경우, 유효기간까지는 아이디/패스워드가 빼앗긴거나 다름이 없는 상태가 되는데요.
이에 대한 대응을 하기 위해서 액세스 토큰과 리프레쉬 토큰을 사용합니다.
유효기간이 짧은 액세스 토큰과 유효기간이 긴 리프레쉬 토큰을 발행해 사용하는데요.
다음과 같이 액세스 토큰은 짧게, 리프레쉬 토큰은 길게 유효기간을 설정해 줍니다.
- 액세스 토큰: 일반적으로 15분에서 1시간 정도로 설정합니다
- 리프레쉬 토큰: 1주일 ~ 1달 정도로 길게 설정
- 리프레시 토큰은 안전하게 저장해야 하며, 유출되지 않도록 주의해야 합니다.
액세스 토큰은 아이디/패스워드와 같으므로 많은 일들을 하고 탈취당할 경우 아주 위험해 집니다.
따라서 유효기간을 짧게 해주고, 유효기간이 다 되면,
유저에게 아이디/패스워드를 물어보는 것이 아니고,
유효기간이 긴 리프레쉬 토큰을 이용해서 서버에서 액세스 토큰을 다시 재 발행해서 사용하도록 합니다.
그럼 유저는 굳이 인증을 하지 않으면서도, 서버는 유효성을 계속 검증할 수 있습니다.
액세스 토큰의 유효기간이 지나고, 리프레쉬 토큰으로 액세스 토큰을 재 발행받으려고 할 때,
해당 클라이언트가 탈취당한 사실을 서버에서 알고 있을 경우,
해당 id로 액세스 토큰을 발급받지 못하도록 막을 수 있겠지요.
이 방법의 단점은, 리프레쉬 토큰을 이용해 액세스 토큰을 발급 받을 때,
매번 서버에 검증을 위해 서버에 접속하므로 부하가 생길 수 있다는 점과,
액세스 토큰의 유효기간동안에는 역시 무방비 상태가 될 수 있다는 점 입니다.
5. 보안관리 팁들
5-1. JWT토큰 전송시 HTTPS 사용
JWT토큰 전송시에는 HTTPS를 사용해서,
통신을 암호화 해 주어야 합니다.
5-2. 토큰 저장시 HTTP 전용 쿠키(HttpOnly Cookie)사용
클라이언트에서 JWT 토큰을 저장할 때, HTTP전용쿠키를 사용해 줍니다.
HTTP 전용 쿠키는 자바스크립트에서 접근할 수 없으므로 XSS 공격으로부터 토큰을 보호할 수 있습니다.
쿠키에 Secure 플래그를 설정하여 HTTPS 연결에서만 쿠키가 전송되도록 합니다.
5-1. 환경변수로 시크릿 키 관리하기
시크릿 키는 환경 변수로 설정하고, 소스 코드에 직접 포함하지 않습니다.
5-2. 시크릿키 정기 관리
secret을 안전하게 보관하고 정기적으로 갱신하는 방안을 마련해야 합니다.
이는 JWT를 사용하는 시스템의 전체 보안 건전성을 유지하는 데 필수적인 부분입니다.
5-3. 꼼꼼한 토큰 검증
서버에서 받은 JWT 토큰을 항상 철저히 검증해야 합니다.
토큰의 유효성, 만료 시간, 서명 등을 확인하고,
검증 실패 시 적절한 에러 처리를 하고,
로그를 남겨 모니터링할 수 있도록 해 두어야 합니다.
5-4. 로그아웃된 토큰 무효화
로그아웃 기능을 구현할 때는 서버 측에서 해당 토큰을 무효화해야 합니다.
무효화된 토큰은 블랙리스트에 추가하거나,
토큰의 식별자를 데이터베이스에 저장하여 관리할 수 있습니다.
로그아웃 시 클라이언트에서도 토큰을 삭제하도록 합니다.
5-5. IP주소 기록하기
JWT 토큰에 사용자의 IP 주소나 기기 정보를 포함하여 토큰 도용이 되더라도 흔적을 찾을 수 있습니다.
5-6. 모니터링
JWT 토큰 사용 시 인증 관련 이벤트를 모니터링하고 로깅하는 것은 안전한 사용을 위해서 매우 중요합니다.
인증 실패, 토큰 검증 오류, 비정상적인 토큰 사용 등을 감지하고,
로그를 분석해 보안 위협을 탐지하고 대응 방안을 수립할 수 있어야 합니다.
'NodeJS, NPM, Koa' 카테고리의 다른 글
Cheerio 와 Bent 이용한 HTML 파싱과 Scraping (0) | 2021.10.15 |
---|---|
Dotenv 로 관리하는 환경변수 # NodeJS (0) | 2021.05.01 |
Koa JS FrameWork을 이용한 RestAPI 만들기 # NodeJS (0) | 2021.05.01 |
WebStorm NodeJS Coding Assistance 활성화 방법 (0) | 2021.04.29 |
NodeJS Get 과 Post 의 Parameter 와 QueryString 전달 방법 (0) | 2021.04.28 |
Babel 을 Webstorm 과 터미널 에 적용하는 방법 # Compiler ES6 (0) | 2021.04.28 |
A Record와 CNAME Record의 차이를 알아보자 (0) | 2020.04.02 |
댓글