쿠키와 세션
쿠키(cookie) : 브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우 해당 데이터를 저장한 뒤 모든 요청에 포함하여 보냅니다.
- 데이터를 여러 사이트에 공유할 수 있기 때문에 보안에 취약할 수 있습니다.
- 쿠키는 userId=user-1321;userName=sparta 와 같이 문자열 형식으로 존재하며 쿠키 간에는 세미콜론(;) 으로 구분됩니다.
세션(session) : 쿠키를 기반으로 구성된 기술로 쿠키와는 다르게 데이터를 서버에 저장합니다.
- 데이터를 서버에 저장하기 때문에 보안이 좋습니다.
- 사용자가 많은 경우 서버에 저장할 데이터가 많아져 서버 컴퓨터가 감당하지 못하는 문제가 생길 수 있습니다.
쿠키 만들어보기
서버가 클라이언트의 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의 헤더를 의미합니다.
app.get("/get-cookie", (req, res) => {
const cookie = req.headers.cookie;
console.log(cookie); // name=sparta
return res.status(200).json({ cookie });
});
cookie-parser 미들웨어 적용하기
쿠키를 사용하기 위해서는 req.headers.cookie 와 같이 여러 프로퍼티를 넘어서야 사용할 수 있습니다.
cookie-parser 미들웨어는 요청에 추가된 쿠키를 req.cookies 객체로 만들어줍니다.
// cookie-parser 미들웨어 전역 사용
app.use(cookieParser());
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 });
});
쿠키를 불러오는 부분이 req.cookies로 바뀌고 쿠키의 형식이 객체 형식으로 바뀌었습니다.
세션 만들어보기
API를 호출했을 때 name=sparta 의 정보를 서버에 저장하고, 저장한 시점의 시간 정보를 쿠키로 반환받는 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();
});
서버에 해당하는 유저의 정보를 저장하기 위한 session 객체를 생성했습니다.
API를 호출했을 때 name = 'sparta' 의 정보를 session에 삽입하고 해당하는 데이터를 불러들이기 위한 시간 정보를 쿠키로 받습니다.
API를 호출했을 때 쿠키의 시간 정보를 이용하여 서버에 저장된 name 정보를 출력하는 API
app.get('/get-session', function (req, res, next) {
const { sessionKey } = req.cookies;
const name = session[sessionKey];
return res.status(200).json({ name });
});
쿠키에 저장된 sessionKey를 이용하여 session에 저장된 데이터를 불러옵니다.
JWT(JsonWebToken)
- JSON 형태의 데이터를 안전하게 교환하여 사용할 수 있게 해줍니다.
- 인터넷 표준으로서 자리잡은 규격입니다.
- 여러가지 암호화 알고리즘을 사용할 수 있습니다.
- header.payload.signature 의 형식으로 3가지의 데이터를 포함합니다. (개미처럼 머리, 가슴, 배) 때문에 JWT 형식으로 변환 된 데이터는 항상 2개의 . 이 포함된 데이터여야 합니다.
- header(머리)는 signature(배)에서 어떤 암호화를 사용하여 생성된 데이터인지 표현합니다.
- payload(가슴)는 개발자가 원하는 데이터를 저장합니다.
- signature(배)는 이 토큰이 변조되지 않은 정상적인 토큰인지 확인할 수 있게 도와줍니다.
- JWT는 암호 키가 없어도 복호화가 가능하기 때문에 민감한 정보(개인정보,비밀번호 등)는 담지 않아야 합니다.
- 쿠키/세션과는 달리 단순히 데이터를 표현하는 방식입니다.
- 서버를 Stateless(무상태)로 관리할 수 있기 때문에 최근 많이 쓰이는 기술입니다.
- Stateless(무상태)란 서버가 언제든 꺼졌다 켜저도 똑같은 동작을 한다면 Stateless 한 것입니다.
JWT 사용하기
설치하기 : 프로젝트 터미널에 입력하기
npm init
npm i jsonwebtoken -S
데이터 암호화
sign을 이용한다.
const jwt = require("jsonwebtoken");
const token = jwt.sign({ myPayloadData: 1234 }, "mysecretkey");
console.log(token);
아래와 같은 결과를 얻는다.
데이터 복호화
decode를 이용한다.
const jwt = require("jsonwebtoken");
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2Njc1NjE0NDB9.nvYSsLsT8jp7IfkbB2seCNeuLqRBgrrzDjKRFXjvoUE";
const decodedValue = jwt.decode(token);
데이터 검증
verify를 이용한다. decode와 같은 동작을 하지만 키가 맞지 않으면 복호화 할 수 없다.
const jwt = require("jsonwebtoken");
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2Njc1NjE0NDB9.nvYSsLsT8jp7IfkbB2seCNeuLqRBgrrzDjKRFXjvoUE";
const decodedValueByVerify = jwt.verify(token, "mysecretkey");
암호화 된 데이터 사용
암호화 된 데이터는 클라이언트(브라우저)가 전달받아 다양한 수단(쿠키, 로컬스토리지 등)을 통해 저장하여 API 서버에 요청을 할 때 서버가 요구하는 HTTP 인증 양식에 맞게 보내주어 인증을 시도합니다.
ex) 놀이공원 자유 이용권
- 회원가입: 회원권 구매
- 로그인: 회원권으로 놀이공원 입장
- 로그인 확인: 놀이기구 탑승 전마다 유효한 회원권인지 확인
- 내 정보 조회: 내 회원권이 목에 잘 걸려 있는지 확인하고, 내 이름과 사진, 바코드 확인
Access Token, Refresh Token
- Access Token은 사용자의 권한이 확인(ex: 로그인) 되었을 경우 해당 사용자를 인증하는 용도로 발급하게됩니다.
- Refresh Token은 Access Token 처럼 해당하는 사용자의 모든 인증 정보를 관리하는 것이 아닌, 특정한 사용자가 Access Token을 발급받을 수 있게 하기 위한 용도로만 사용됩니다.
Access Token, Refresh Token 발급해보기
패키지 설치하기
npm init -y
npm install express jsonwebtoken cookie-parser -S
서버 파일 생성
// app.js
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
const express = require("express");
const app = express();
const port = 3002;
const SECRET_KEY = `여기에 키가 들어갑니다`;
// cookie-parsrt 미들웨어 사용하기
app.use(cookieParser());
app.get("/", (req, res) => {
res.status(200).send("Hello Token!");
})
app.listen(port, () => {
console.log(port, '포트로 서버가 열렸어요!');
})
cookie-parser 미들웨어는 요청에 추가된 쿠키를 req.cookies 객체로 만들어 줍니다.
토큰 발급받기
// app.js
...
let tokenObject = {}; // Refresh Token을 저장할 Object
app.get("/set-token/:id", (req, res) => {
const id = req.params.id;
const accessToken = createAccessToken(id);
const refreshToken = createRefreshToken();
tokenObject[refreshToken] = id; // Refresh Token을 가지고 해당 유저의 정보를 서버에 저장합니다.
res.cookie('accessToken', accessToken); // Access Token을 Cookie에 전달한다.
res.cookie('refreshToken', refreshToken); // Refresh Token을 Cookie에 전달한다.
return res.status(200).send({ "message": "Token이 정상적으로 발급되었습니다." });
})
// Access Token을 생성합니다.
function createAccessToken(id) {
const accessToken = jwt.sign(
{ id: id }, // JWT 데이터
SECRET_KEY, // 비밀키
{ expiresIn: '10s' }) // Access Token이 10초 뒤에 만료되도록 설정합니다.
return accessToken;
}
// Refresh Token을 생성합니다.
function createRefreshToken() {
const refreshToken = jwt.sign(
{}, // JWT 데이터
SECRET_KEY, // 비밀키
{ expiresIn: '7d' }) // Refresh Token이 7일 뒤에 만료되도록 설정합니다.
return refreshToken;
}
토큰 검증
// app. js
...
app.get("/get-token", (req, res) => {
const accessToken = req.cookies.accessToken;
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.status(400).json({ "message": "Refresh Token이 존재하지 않습니다." });
if (!accessToken) return res.status(400).json({ "message": "Access Token이 존재하지 않습니다." });
const isAccessTokenValidate = validateAccessToken(accessToken);
const isRefreshTokenValidate = validateRefreshToken(refreshToken);
if (!isRefreshTokenValidate) return res.status(419).json({ "message": "Refresh Token이 만료되었습니다." });
if (!isAccessTokenValidate) {
const accessTokenId = tokenObject[refreshToken];
if (!accessTokenId) return res.status(419).json({ "message": "Refresh Token의 정보가 서버에 존재하지 않습니다." });
const newAccessToken = createAccessToken(accessTokenId);
res.cookie('accessToken', newAccessToken);
return res.json({ "message": "Access Token을 새롭게 발급하였습니다." });
}
const { id } = getAccessTokenPayload(accessToken);
return res.json({ "message": `${id}의 Payload를 가진 Token이 성공적으로 인증되었습니다.` });
})
// Access Token을 검증합니다.
function validateAccessToken(accessToken) {
try {
jwt.verify(accessToken, SECRET_KEY); // JWT를 검증합니다.
return true;
} catch (error) {
return false;
}
}
// Refresh Token을 검증합니다.
function validateRefreshToken(refreshToken) {
try {
jwt.verify(refreshToken, SECRET_KEY); // JWT를 검증합니다.
return true;
} catch (error) {
return false;
}
}
// Access Token의 Payload를 가져옵니다.
function getAccessTokenPayload(accessToken) {
try {
const payload = jwt.verify(accessToken, SECRET_KEY); // JWT에서 Payload를 가져옵니다.
return payload;
} catch (error) {
return null;
}
}
'내일배움캠프 노드 4기 > Today I Learned' 카테고리의 다른 글
22/12/23 TIL - sequelize 문법, http status code (0) | 2022.12.23 |
---|---|
express [Error: Cannot find module 'html'] (0) | 2022.12.22 |
[mongoose] 몽구스 사용해보기 (0) | 2022.12.19 |
서버와 도메인 연결하기 - gabia (0) | 2022.12.16 |
AWS 배포하기 (0) | 2022.12.16 |