주특기 2주차 Node.js 숙련 - JWT
2022. 12. 26. 02:01ㆍ항해99/3~5주차 - 주특기
1. 쿠키와 세션
- 쿠키와 세션이란?
- 쿠키(Cookie): 브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우 해당 데이터를 저장한 뒤 모든 요청에 포함하여 보냄
- 데이터를 여러 사이트에 공유할 수 있기 때문에 보안에 취약할 수 있음
- 쿠키는 userId=user-1321;userName=sparta 와 같이 문자열 형식으로 존재하며 쿠키 간에는 세미콜론(;) 으로 구분됨
- 세션(Session): 쿠키를 기반으로 구성된 기술임. 단, 클라이언트가 마음대로 데이터를 확인 할 수 있던 쿠키와는 다르게 세션은 데이터를 서버에만 저장하기 때문에 보안이 좋으나, 반대로 사용자가 많은 경우 서버에 저장해야 할 데이터가 많아져서 서버 컴퓨터가 감당하지 못하는 문제가 생기기 쉬움
- 쿠키(Cookie): 브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우 해당 데이터를 저장한 뒤 모든 요청에 포함하여 보냄
- 쿠키(Cookie) 만들어보기
- 서버가 클라이언트의 HTTP 요청(Request)을 수신할 때, 서버는 응답(Response)과 함께 Set-Cookie 라는 헤더를 함께 전송할 수 있다
- 그 후 쿠키는 해당 서버에 의해 만들어진 응답(Response)과 함께 Cookie HTTP 헤더안에 포함되어 전달받는다
//Set-Cookie를 이용하여 쿠키 할당하기
app.get("/set-cookie", (req, res) => {
const expire = new Date();
expire.setMinutes(expire.getMinutes() + 60); // 만료 시간을 60분으로 설정합니다.
res.writeHead(200, {
'Set-Cookie': `name=sparta; Expires=${expire.toGMTString()}; HttpOnly; Path=/`,
});
return res.status(200).end();
});
//res.cookie()를 이용하여 쿠키 할당하기
app.get("/set-cookie", (req, res) => {
const expires = new Date();
expires.setMinutes(expires.getMinutes() + 60); // 만료 시간을 60분으로 설정합니다.
res.cookie('name', 'sparta', {
expires: expires
});
return res.status(200).end();
});
- req를 이용하여 쿠키 접근하기
- 일반적으로 쿠키는 req.headers.cookie에 들어있다.
- req.headers는 클라이언트가 요청한 Request의 헤더를 의미함
- 클라이언트는 서버에 요청(Request)을 보낼 때 자신이 보유하고 있는 쿠키를 자동으로 서버에 전달
- 여기서 클라이언트가 전달하는 쿠키 정보는 Request header에 포함되어 서버에 전달되게 됨
//get-cookie에 접근했을 때, 설정된 쿠키를 출력하는 API
//req.headers에서 전달된 쿠키를 출력하는 예제
app.get("/get-cookie", (req, res) => {
const cookie = req.headers.cookie;
console.log(cookie); // name=sparta
return res.status(200).json({ cookie });
});
- cookie-parser 미들웨어 적용하기
- cookie-parser 미들웨어는 요청에 추가된 쿠키를 req.cookies 객체로 만들어 줍니다. 더이상 req.headers.cookie와 같이 번거롭게 사용하지 않아도 됨
- 쿠키를 사용하기 위해서는req.headers.cookie와 같이 여러 프로퍼티를 넘어서야 사용할 수 있었음
- cookie-parser 사용하기
//cookie-parser를 이용해 쿠키를 출력하는 API
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get("/get-cookie", (req, res) => {
const cookie = req.cookies;
console.log(cookie); // { name: 'sparta' }
return res.status(200).json({ cookie });
});
- 세션(Session) 만들어보기
- 쿠키의 경우 서버를 재시작하거나 새로고침을 하더라도 로그인이 유지됨
- 사용자의 입장에서는 편하게 사용할 수 있지만 서버의 입장에서는 상당히 위험함
- 쿠키가 조작되거나 노출되어 보안적으로 문제가 발생할 수 있음
//set-session API 만들기
let session = {};
app.get('/set-session', function (req, res, next) {
const name = 'sparta';
const uniqueInt = Date.now();
session[uniqueInt] = { name };
res.cookie('sessionKey', uniqueInt);
return res.status(200).end();
});
//get-session API 만들기
app.get('/get-session', function (req, res, next) {
const { sessionKey } = req.cookies;
const name = session[sessionKey];
return res.status(200).json({ name });
});
//API 전체코드
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
let session = {};
app.get('/set-session', function (req, res, next) {
const name = 'sparta';
const uniqueInt = Date.now();
session[uniqueInt] = { name };
res.cookie('sessionKey', uniqueInt);
return res.status(200).end();
});
app.get('/get-session', function (req, res, next) {
const { sessionKey } = req.cookies;
const name = session[sessionKey];
return res.status(200).json({ name });
});
app.listen(5002, () => {
console.log("서버가 켜졌어요!");
});
2. JWT
- 간략한 정리
- JSON 형태의 데이터를 안전하게 교환하여 사용할 수 있게 해줌
- 인터넷 표준으로서 자리잡은 규격
- 여러가지 암호화 알고리즘을 사용할 수 있음
- header.payload.signature 의 형식으로 3가지의 데이터를 포함합니다.
- 때문에 JWT 형식으로 변환 된 데이터는 항상 2개의 . 이 포함된 데이터여야 합니다.
- 규격
- header(머리)는 signature(배)에서 어떤 암호화를 사용하여 생성된 데이터인지 표현
- payload(가슴)는 개발자가 원하는 데이터를 저장
- signature(배)는 이 토큰이 변조되지 않은 정상적인 토큰인지 확인할 수 있게 도와줌
- 더 알아두어야 할 특성
- JWT는 암호 키(Secret Key)를 모르더라도 복호화(Decode)가 가능함
- 변조만 불가능 할 뿐, 누구나 복호화하여 보는것은 가능하다는 의미가 됨
- 때문에 민감한 정보(개인정보, 비밀번호 등)는 담지 않도록 해야함
- 특정 언어에서만 사용 가능한것은 아님
- 단지 개념으로서 존재하고, 이 개념을 코드로 구현하여 공개된 코드를 우리가 사용하는게 일반적임
- 쿠키, 세션 차이
- 데이터를 교환하고 관리하는 방식인 쿠키/세션과 달리, JWT는 단순히 데이터를 표현하는 형식임
- JWT로 만든 데이터를 브라우저로 보내도 쿠키처럼 자동으로 저장되지는 않지만, 변조가 거의 불가능하고 서버에 데이터를 저장하지 않기 때문에 서버를 Stateless(무상태)로 관리할 수 있기 때문에 최근 많이 쓰이는 기술중 하나
- Stateless(무상태)와 Stateful(상태 보존)의 차이를 간단히 설명하자면, Node.js 서버가 언제든 죽었다 살아나도 똑같은 동작을 하면 Stateless하다고 볼 수 있음
- 반대로 서버가 죽었다 살아났을때 조금이라도 동작이 다른 경우 Stateful하다고 볼 수 있음
- 서버가 스스로 어떤 기억을 갖고 다른 결정을 하냐 마냐의 차이라고 보면 더 쉬움
- 로그인 정보를 서버에 저장하게 되면 무조건 Stateful(상태 보존)이라고 볼 수 있음
3. JWT는 어떻게 사용법
- 오픈소스 라이브러리를 이용합니다.
- 프로젝트로 이용할 폴더를 생성하고, 모듈을 설치해줌
- 나는 제일 사용량이 많은 jsonwebtoken 라이브러리를 사용할 것!
npm init
npm i jsonwebtoken -S
- 원하는 JSON 데이터를 암호화
const jwt = require("jsonwebtoken");
const token = jwt.sign({ myPayloadData: 1234 }, "mysecretkey");
console.log(token);
- 복호화하기
const jwt = require("jsonwebtoken");
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2Njc1NjE0NDB9.nvYSsLsT8jp7IfkbB2seCNeuLqRBgrrzDjKRFXjvoUE";
const decodedValue = jwt.decode(token);
- 변조된 데이터 검증하기
const jwt = require("jsonwebtoken");
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2Njc1NjE0NDB9.nvYSsLsT8jp7IfkbB2seCNeuLqRBgrrzDjKRFXjvoUE";
const decodedValueByVerify = jwt.verify(token, "mysecretkey");
- 암호화 된 데이터 사용방법
- 보통 암호화 된 데이터는 클라이언트(브라우저)가 전달받아 다양한 수단(쿠키, 로컬스토리지 등)을 통해 저장하여 API 서버에 요청을 할 때 서버가 요구하는 HTTP 인증 양식에 맞게 보내주어 인증을 시도합니다!
4. Access Token, Refresh Token
- Access Token이란
- Access Token은 사용자의 권한이 확인(ex: 로그인) 되었을 경우 해당 사용자를 인증하는 용도로 발급하는 토큰
- Cookie로 jwt를 발급하고 설정한 Expire 기간이 지날 때 인증이 만료되게 하는것 또한 Access Token이라고 부를 수있음
- 사용자가 Access Token을 가지고 인증을 요청할 경우 Token을 생성할 때 사용한 비밀키(Secret Key)를 가지고 인증하기 때문에, 복잡한 설계없이 코드를 구현할 수 있고, 여러 분기를 거치지 않아도 된다는 장점이 있음
- Stateless(무상태) 즉, Node.js 서버가 죽었다 살아나더라도 동일한 동작을하는 방식임
- jwt를 이용해 사용자의 인증 여부는 확인할 수 있지만, 처음 발급한 사용자 본인인지 확인할 수는 없음
- Access Token은 그 자체로도 사용자를 인증하는 모든 정보를 가지고 있다. 그렇기 때문에 토큰을 가지고 있는 시간이 늘어날 수록 탈취되었을 때는 피해가 더욱 커지게 됨
- 만약 토큰이 탈취되었다고 인지하더라도 저희들은 해당 토큰이 탈취된 토큰인지 알 수 없고, 고의적으로 만료를 시킬 수도 없음. 그러므로 언제든지 사용자의 토큰이 탈취될 수 있다고 생각을 하고, 피해를 최소화 할 수 있는 방향으로 개발을 진행해야함
- Refresh Token이란
- Refresh Token은 Access Token 처럼 해당하는 사용자의 모든 인증 정보를 관리하는 것이 아닌, 특정한 사용자가 Access Token을 발급받을 수 있게 하기 위한 용도로만 사용되는 토큰
- Refesh Token은 사용자의 인증정보를 사용자가 가지고 있는 것이 아닌, 서버에서 해당 사용자의 정보를 저장소 또는 별도의 DB에 저장하여 관리함
- 서버에서 특정 Token 만료가 필요할 경우 저장된 Token을 제거하여 사용자의 인증 여부를 언제든지 제어가 가능하다는 장점이 있음
- 그렇다면 어째서 바로 Access Token을 발급하지 않고, Refresh Token을 거쳐서 Access Token을 발급하는것일까
- 사용자에게 발급한 Token이 탈취당할 경우 피해를 최소화 하기 위해서
- 실제 세계에서 사용하는 OTP와 같이 짧은 시간 내에서만 인증 정보를 사용할 수 있게하고, 주기적으로 재발급하여, 토큰이 유출되더라도 오랜 기간동안 피해를 입는것이 아닌, 짧은 기간동안만 사용가능하도록 하여 피해를 최소화할 수 있게 됨
- 언제든지 토큰이 탈취될 수 있다는 것을 가정하고, 탈취를 막는것이 어렵다면, 탈취된 토큰자체를 사용할 수 있는 기간을 줄여서 피해를 막을 것
'항해99 > 3~5주차 - 주특기' 카테고리의 다른 글
주특기 2주차 Node.js 숙련 - MySQL → Sequelize (1) | 2023.01.18 |
---|---|
주특기 2주차 Node.js 숙련 - 미들웨어 (Validation Check) (0) | 2023.01.18 |
주특기 1주차 Node.js 입문 - mongoose (0) | 2022.12.25 |
주특기 1주차 Node.js 입문 - Express.js (0) | 2022.12.25 |
주특기 1주차 Node.js 입문 - Packge Manager (0) | 2022.12.25 |