반응형
🧐 Access Token
Access Token은 사용자의 인증이 완료된 후 해당 사용자를 인증하는 용도로 발급하는 토큰이다.
쿠키(Cookie)에 JWT를 설정하고 지정된 만료 시간이 지나면 인증이 만료되는 구조 또한 Access Token이라고 부를 수 있다.
Access Token은 Stateless(무상태) 즉, Node.js서버가 재시작되더라도 동일하게 작동한다.
JWT를 이용해서 사용자 인증 여부는 확인 할 수 있지만, 처음 토큰을 발급한 사용자가 그 사용자인지는 확인 할 수 없다. 즉, 처음 토큰을 발급한 사람의 JWT가 누군가의 의해 탈취당해도 탈취당한 토큰인지 알 수 없다. 그러므로 개발자는 Access Token의 만료기간을 짧게 설정하여 이러한 피해를 대비할 수 있어야한다.
🧐 Refresh Token
Refresh Token은 사용자의 모든 인증 정보를 담고 있는 Access Token과는 달리, 특정 사용자가 Access Token을 발급받기 위한 목적으로만 사용된다.
Refresh Token은 사용자 인증 정보를 검증하는데 사용되며, 이를 서버에서 관리한다.
그렇기에 사용자 인증 상태를 언제든 서버에서 제어할 수 있다는 장점이 있다.
Refresh Token을 통해 다시 Access Token을 발급하는 것은 탈취당한 경우에 대비해서 피해를 최소화하기 위해서이다.
🧐 코드 구현
// app.js
import express from "express";
import jwt from "jsonwebtoken";
import cookieParser from "cookie-parser";
const app = express();
const PORT = 3019;
// 비밀 키는 외부에 노출되면 안되겠죠? 그렇기 때문에, .env 파일을 이용해 비밀 키를 관리해야합니다.
const ACCESS_TOKEN_SECRET_KEY = `HangHae99`; // Access Token의 비밀 키를 정의합니다.
const REFRESH_TOKEN_SECRET_KEY = `Sparta`; // Refresh Token의 비밀 키를 정의합니다.
app.use(express.json());
app.use(cookieParser());
app.get("/", (req, res) => {
return res.status(200).send("Hello Token!");
});
const tokenStorages = {}; //리프레쉬 토큰을 관리할 객체
//만약 프로젝트나 비즈니스로직에서는 데이터베이스에 테이블을 형성하고 저장
function createAccessToken(id) {
return jwt.sign({ id }, ACCESS_TOKEN_SECRET_KEY, {
expiresIn: "10s",
});
}
// 엑세스, 리프레쉬 토큰 발급 api
app.post("/tokens", async (req, res) => {
const { id } = req.body;
const accessToken = createAccessToken(id);
const refreshToken = jwt.sign({ id: id }, REFRESH_TOKEN_SECRET_KEY, {
expiresIn: "7d",
});
tokenStorages[refreshToken] = {
id: id,
ip: req.ip,
userAgent: req.headers["user-agent"],
};
console.log(tokenStorages);
// 클라이언트에게 쿠키(토큰)를 할당
res.cookie("accessToken", accessToken);
res.cookie("refreshToken", refreshToken);
return res
.status(200)
.json({ message: "Token이 정상적으로 발급되었습니다." });
});
// Access Token 검증 API
app.get("/tokens/validate", async (req, res) => {
const { accessToken } = req.cookies;
// 엑세스 토큰이 존재하는지 확인한다.
if (!accessToken) {
return res
.status(400)
.json({ errorMessage: "Access Token이 존제하지 않습니다." });
}
const payload = validateToken(accessToken, ACCESS_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: "Access Token이 정상적이지 않습니다." });
}
const { id } = payload;
return res.status(200).json({
message: `${id}의 payload를 가진 Token이 정상적으로 인증 되었습니다.`,
});
});
// Token을 검증하고 Payload를 조회하기 위한 함수
function validateToken(token, secrestKey) {
try {
return jwt.verify(token, secrestKey);
} catch (err) {
return null;
}
}
// Refresh Token을 이용해서, Access Token을 재발급하는 API
app.post("/tokens/refresh", async (req, res) => {
const { refreshToken } = req.cookies;
if (!refreshToken) {
return res
.status(400)
.json({ errorMessage: "Refresh Token이 존재하지 않습니다." });
}
const payload = validateToken(refreshToken, REFRESH_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: "Refrsh Token이 정상적이지 않습니다." });
}
const userInfo = tokenStorages[refreshToken];
if (!userInfo) {
return res.status(401).json({
errorMessage: "Refresh Token의 정보가 서버에 존재하지 않습니다.",
});
}
const newAccessToken = createAccessToken(userInfo.id); //다시 access토큰을 발급
res.cookie("accessToken", newAccessToken);
return res
.status(200)
.json({ message: "Access Token을 정상적으로 새롭게 발급했습니다." });
});
app.listen(PORT, () => {
console.log(PORT, "포트로 서버가 열렸어요!");
});
반응형
'TIL' 카테고리의 다른 글
[TIL] 편메추 프로젝트 시작 (0) | 2024.02.08 |
---|---|
[TIL] express-session (0) | 2024.02.06 |
[TIL] JWT(Json Web Token) (0) | 2024.02.02 |
[TIL] 개인과제 초기 설계 (0) | 2024.01.31 |
[TIL] 쿠키와(Cookie) 세션(Session) (0) | 2024.01.30 |
댓글