Header | Payload | Signature |
---|---|---|
토큰의 한 종류 암호화 알고리즘 | 유저 정보, 사용자 권한, 기타 필요한 정보 | Header + Payload를 base64로 인코딩한 값에 secret을 더해 암호화 |
* secret : 열쇠 역할을 하는 암호 |
JWT 값이 바뀌면 유효하지 않은 값이 됨
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30ㅓ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30ㅓ
Base64 Decode and Encode - Online
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}
‼ base64 인코딩은 원한다면 얼마든지 디코딩이 가능하므로 payload에 민감한 정보 넣지 않기
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
const secretKey = "secret-key"
// POST 요청 (로그인 요청시 보내는 메소드)
app.post("/", (req, res) => {
// 2️⃣. 요청 바디에서 전달받은 값을 구조분해 할당을 사용하여 관리
const {userId, userPassword} = req.body;
// 3️⃣. (find 메서드를 사용하여) users의 정보와 사용자가 입력한 정보를 비교하여 일치하는 회원이 존재하는지 확인하는 로직
const userInfo = users.find(el => el.user_id === userId && el.user_password === userPassword);
console.log(req.body)
if (!userInfo) {
res.status(401).send("로그인 실패");
} else {
// 유저가 존재하는 경우 user의 id 정보를 세션에 저장
jwt.sign({userId: userInfo.user_id}, secretKey, {expiresIn : 1000 * 60 * 10})
res.send("⭐️세션 생성 완료!");
}
});
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJvel91c2VyMyIsImlhdCI6MTc0MjM1NTI2OCwiZXhwIjoxNzQyOTU1MjY4fQ.vpsvf_IDxfdqTgh7KdzfwOuobrH2MLpiMEoR9tj2cFs
if (!userInfo) {
res.status(401).send("로그인 실패");
} else {
// 유저가 존재하는 경우 user의 id 정보를 세션에 저장
const accessToken = jwt.sign({userId: userInfo.user_id}, secretKey, {expiresIn : 1000 * 60 * 10})
res.cookie("accessToken", accessToken)
res.send("토큰 생성 완료!");
}
req.cookies
에서 accessToken
을 구조 분해 할당으로 추출
jwt.verify()
를 사용하여 accessToken
을 검증하고, 토큰의 페이로드(payload)를 추출
secretKey
는 JWT를 서명(sign)하고 검증할 때 사용하는 비밀 키users
배열에서 JWT 페이로드에 있는 userId
와 일치하는 사용자 정보를 찾음
find()
메서드를 사용하여 조건(el.user_id === payload.userId
)을 만족하는 첫 번째 요소를 반환userInfo
에 저장됨userInfo
를 JSON 형식으로 클라이언트에 응답
// GET 요청
app.get("/", (req, res) => {
const {accessToken} = req.cookies
const payload = jwt.verify(accessToken, secretKey)
const userInfo = users.find(el => el.user_id === payload.userId)
return res.json(userInfo);
});
accessToken이 저장된 쿠키를 삭제함으로써 로그아웃
// DELETE 요청
app.delete("/", (req, res) => {
res.clearCookie('accessToken')
res.send("🧹세션 삭제 완료");
});
if (!userInfo) {
res.status(401).send("로그인 실패");
} else {
// 유저가 존재하는 경우 user의 id 정보를 세션에 저장
const accessToken = jwt.sign({userId: userInfo.user_id}, secretKey, {expiresIn : 1000 * 60 * 10})
res.send(accessToken);
}
let accessToken = ""
// 로그인 함수
function login() {
const userId = idInput.value;
const userPassword = passwordInput.value;
return axios.post("http://localhost:3000", { userId, userPassword })
.then(res => accessToken = res.data)
}
// 유저 정보를 받아오는 함수
function getUserInfo() {
return axios.get("http://localhost:3000", {
header : { 'Authorization' : `Bearer ${accessToken}`}
});
}
// GET 요청
app.get("/", (req, res) => {
const accessToken = req.headers.authorization.split(" ")[1];
const payload = jwt.verify(accessToken, secretKey)
const userInfo = users.find(el => el.user_id === payload.userId)
return res.json(userInfo);
});
클라리언트 측에서 accessToken을 초기화하면 됨
// 로그아웃 함수
function logout() {
accessToken = ''
}
// 로그아웃 버튼을 클릭하는 경우
logoutButton.onclick = () => {
logout()
renderLoginForm();
};
종류 | 특징 | 상태 저장 위치 | 장점 | 단점 |
---|---|---|---|---|
쿠키 | 클라이언트에 저장되는 짧은 텍스트 HTTP의 무상태성 보완 | 클라이언트 | HTTP 요청과 응답의 정보를 저장할 수 있다. 자동으로 서버에 전달된다. | 쿠키 자체는 인증을 위한 것이 아니다. |
세션 | 인증 정보를 서버에 저장하고 관리 세션 아이디를 쿠키로 전송 | 서버 | 서버에서 인증 정보를 관리해 보안성이 좋다. | 로그인한 사용자가 많아지면 서버에 부하가 걸릴 수 있다. 서버 분산이 어렵다. |
토큰 | 토큰 자체로 인증 상태 증명 가능 클라이언트에 인증 정보 저장 | 클라이언트 | 인증 정보를 클라이언트에 저장해 서버의 부하를 줄여준다. | 토큰이 탈취당하면 무효화하기 힘들다. |