2023. 7. 16. 14:48ㆍnode.js
JWT 토큰이란?
Json 포멧을 이용해 사용자에 대한 속성을 저장하는 웹 토큰이다.
JWT 는 토큰 자체를 정보로 사용한다. 주로 회원 인증이나 정보 전달에 사용된다.
JWT 구조
JWT 는 Header, Payload, Signature 의 세가지 부분으로 이루어 지고,
Json 형태인 각 부분은 Base64Url 로 인코딩 되어 표현된다.
또 각 부분을 이어주기 위해서 . 구분자를 사용하여 구분한다.
* Base64Url 은 암호화된 문자열이 아니고 같은 문자열에 대해 항상 같은 인코딩 문자열을 반환한다.
1. Header(헤더)
토큰의 헤더는 typ 과 alg 두 가지 정보로 구성된다.
{
"alg": "HS256",
"typ": JWT
}
typ : 토큰의 타입을 지정 ex) JWT
alg : 알고리즘 방식을 지정하며, 서명 및 토큰 검증에 사용된다. ex) HS256, RSA
2. PayLoad (페이로드)
토큰의 페이로드에는 토큰에서 사용할 정보의 조각들인 클레임(Claim) 이 담겨있다.
클레임은 총 3가지 이며 key/value 형태로 다수의 정보를 넣을 수 있다.
3. Signature (서명)
토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드.
서명을 위해서 만든 헤더와 페이로드의 값을 각각 BASE64Url 로 인코딩 하고, 인코딩한 값을 비밀 키를 이용해 헤더에서 정의한 알고리즘으로 해싱하고 이 값을 다시 BASE64Url 로 인코딩하여 생성한다.
고려사항
- 토큰 자체에 정보를 담고 있어 양날의 검이 될 수 있다.
- 토큰이 페이로드에 3종류의 클레임을 저장하기 때문에 정보가 많아질 수록 토큰의 길이가 늘어나 네트워크 부하를 줄 수 있다.
- 페이로드 자체는 암호화 된 것이 아니라, BASE64Url 로 인코딩 된 것이다. 중간에 페이로드를 탈취하여 디코딩 당하면 데이터를 볼 수 있으므로 암호화하거나 페이로드에 중요 데이터를 넣지 않아야 한다.
- JWT 는 상태를 저장하지 않기 때문에 한번 만들어지면 제어가 불가능하므로, 토큰을 임의로 삭제하는 것이 불가능해 토큰 만료 시간을 꼭 넣어주어야 한다.
- 토큰은 클라이언트 측에서 관리해야 하기 때문에 토큰을 저장해야 한다.
코드 예시
npm i jsonwebtoken
login.controller.js
// ...
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
// ...
refreshToken = []
// ...
const { nickname, password } = req.body;
const user = await this.loginService.loginUser(nickname);
const validPassword = await bcrypt.compare(password, user.password);
const token = jwt.sign({ nickname }, 'jwt_secret', {
expiresIn: "1h",
});
const refreshToken = jwt.sign(
{ nickname },
'jwt_refresh_secret',
{
expiresIn: "14d",
}
);
this.refreshTokens.push(refreshToken);
res.cookie("Authorization", `Bearer ${token}`);
res.cookie("RefreshToken", refreshToken);
return res.status(200).json({ token: token });
// ...
auth-middleware.js
const jwt = require("jsonwebtoken");
const { Users } = require("../models");
require("dotenv").config();
const generateAccessToken = (nickname) => {
console.log("accessToken 재생성");
const accessToken = jwt.sign({ nickname }, process.env.JWT_SECRET, {
expiresIn: "1h",
});
return accessToken;
};
module.exports = async (req, res, next) => {
try {
const { Authorization, RefreshToken } = req.cookies;
const [type, token] = (Authorization ?? "").split(" ");
if (!type || type !== "Bearer" || !token) {
return res
.status(403)
.json({ errorMessage: "로그인이 필요한 기능입니다." });
}
const decodeToken = jwt.verify(token, process.env.JWT_SECRET, {
ignoreExpiration: true, // 만료 여부 검사를 무시
//ignoreExpiration 옵션을 true로 설정하여 만료 여부 검사를 무시합니다. 만료된 경우에도 검사를 통과한다.
});
const nickname = decodeToken.nickname;
const findUser = await Users.findOne({ where: { nickname } });
if (!findUser) {
return res
.status(403)
.json({ errorMessage: "로그인이 필요한 기능입니다." });
}
res.locals.user = findUser;
// Refresh Token 유효성 검사
if (!RefreshToken) {
return res
.status(403)
.json({ errorMessage: "로그인이 필요한 기능입니다." });
}
const decodeRefreshToken = jwt.verify(
RefreshToken,
process.env.JWT_REFRESH_SECRET
);
const refreshNickname = decodeRefreshToken.nickname;
if (nickname !== refreshNickname) {
return res.status(403).json({
errorMessage:
"Refresh Token과 Access Token의 사용자가 일치하지 않습니다.",
});
}
// Access Token이 만료되었을 경우 재발급
const newAccessToken = generateAccessToken(nickname);
res.setHeader("Authorization", `Bearer ${newAccessToken}`);
next();
} catch (err) {
console.error(err);
return res
.status(403)
.json({ errorMessage: "전달된 쿠키에서 오류가 발생했습니다." });
}
};
그냥 서버 배열에 refreshtoken 을 저장했지만 실제 환경에서는 redis 에 저장해야 할 것 같음.
https://mangkyu.tistory.com/56
'node.js' 카테고리의 다른 글
에러처리 미들웨어 [ express ] [ node.js ] (0) | 2023.07.22 |
---|---|
multer (0) | 2023.07.20 |
bcrypt 사용방법 [ Javascript ] [ node.js ] (0) | 2023.07.07 |
PM2 사용 (0) | 2023.06.24 |
노드 내장 객체 : global [ node.js 교과서 ] (0) | 2023.04.10 |