session
은user
가 정보를 요청할 때마다 데이터 베이스를 뒤져가며user
가 보낸session id
와 일치하는user의 id, password조합
이 있는지 찾아야한다. 이것은 꽤나 불편한 일이고 부담스러운 일이다.
이러한 불편을 해소하기 위해token
을 사용한다.
https://www.youtube.com/watch?v=tosLBcAX1vk
간단하게 말하면 session과 달리 server입장에서 user가 요청을 보낼 때 매번 DB를 뒤질 필요 없이 로그인 인증을 가능하게 해주는 녀석이다
대표적인 녀석으로 JWT token이 존재하고 두 가지 토큰을 사용한다.
1.Access Token : 실제로 권한을 얻는데 사용됨
2.Refresh Token : token정보가 만료되거나 탈취되었을 경우 새로운 access token을 발급받을 수 있게 해준다
const { Users } = require('../../models');
const jwt = require('jsonwebtoken');
require("dotenv").config() //이거는 뭐냐..? -> 환경변수 env파일 정보 들고오는 라이브러리인듯
// sign 은 토큰화, 본문 => 암호문
// verify 토큰화를 본문, 암호문 => 본문
module.exports = (req, res) => {
// <access token을 가지고 있는 client가 서버로 유저정보를 요청할 때 처리해주는 모듈이다.>
// Authorization header에 담은 access token이 유효한지 확인한다 즉, 서버가 가지고 있는 비밀 키로 이전에 생성한 토큰이 맞는지 확인한다.
// 이 경우에도 Authorization header에 유효한 토큰이 있는지 없는지를 분기를 만들어 작성한다.
// 만약 Aturhorization header에 정상적인 토큰이 있다면, 토큰을 해독해서 얻은 payload 즉 데이터 유저 정보를(필요한 정보 : id, userId, email, createdAt, updatedAt)를 반환한다.
// 만약 Aturhorization header에 토큰 자체가 없거나, 서버가 들고 있는 비밀 키로 해독할 수 없는 경우 {"data" : null, "message" : "invalid access token"}
let authorization = req.headers.authorization; //client가 request한 데이터의 headers에서 authorization을 들고온다.
if(authorization) {
// <클라이언트가 보낸 요청에 인증서가 존재하는 경우>
authorization = authorization.substr(7); //모든 access token에는 Bearer가 붙기 때문에 substr으로 잘라줬다(다른 방법으로 잘라줘도 상관은 없음)
const draft = jwt.verify(authorization, process.env.ACCESS_SECRET);
//근데 만약 이 해독한 본문이 정상적일 경우와 정상적이지 않을 경우를 나눠서 생각해본다
//***jwt.veryfy(해독하고 싶은 token을 넣어준다, token을 해독할 수 있는 key값을 넣어준다, [options])
if(draft) {
// <클라이언트에서 보낸 request에 인증서가 있고 verify로 해독했을 때 정상적인 데이터가 있는 경우(ACCESS_SECRET으로 풀었을 때 정상적으로 풀리는 경우)>
const requiredData = { //client에게 responsefh 돌려줘야 하는 데이터
"id" : draft.id,
"userId" : draft.userId,
"email" : draft.email,
"createdAt" : draft.createdAt,
"updatedAt" : draft.updatedAt
}
return res.json({data:{userInfo: requiredData}, "message" :"ok"}).end() //정상 메세지와 client에게 데이터를 전달해준다
} else {
// <클라이언트에서 보낸 request에 인증서는 있지만 verify로 해독했을 때 정상적인 데이터가 없는 경우(ACCESS_SECRET으로 풀었을 때 정상적으로 풀리지 않는 경우)>
return res.json({"data" : null, "message" :"invalid access token"}).end() //에러 메세지를 보내준다
}
} else {
// <클라이언트가 보낸 요청에 인증서가 존재하지 않는 경우>
return res.json({"data" : null, "message" :"invalid access token"}).end() //에러 메세지를 보내준다
}
};
const { Users } = require("../../models");
const jwt = require('jsonwebtoken');
module.exports = async (req, res) => {
// <로그인 요청에 응답하는 프로세스를 만들어주는 곳이다>
//로그인에 필요한 정보 req.body.userId, req.body.password를 받아왔다.
//client가 요청을 보냈을 때 로그인 정보가 있는 경우와 (요청한 데이터 중에 database에 일치하는 것이 있다면 id, userId, email, createdAt, updatedAt을 token의 payload에 담아 JWT token을 생성한다)
//그리고 만든 refresh token을 res.cookie에 담아주자(만약 서버에서 처음 클라이언트에게 쿠키를 보낸다면 Set-Cookie를 사용해서 넘겨주자)
//여기까지 성공하면 {"data" : {"accessToken" : 생성한_access_token}, "message": "ok"}
//client가 요청을 보냈을 때 로그인 정보가 없는 경우를 구분해서 만들어주자(요청한 데이터 중에 database에 일치하는 것이 없다면 {"data" : null, "message" : "not authorized"}를 반환하자)
// TODO: urclass의 가이드를 참고하여 POST /login 구현에 필요한 로직을 작성하세요.
// HINT: auth-session과 마찬가지로, sequelize를 사용해 DB를 조회하세요. 이 조회를 통해서 client가 요청한 userId, password와 일치하는 데이터가 DB에 존재하는지 파악한다.
const data = await Users.findOne({
where: { userId: req.body.userId, password: req.body.password },
});
// console.log(data)를 찍어서 data가 어떻게 생겼는지 보고 필요한 값들을 들고온다
// let id = data.id
// let userId = data.userId
// let email = data.email
// let createdAt = data.createdAt
// let updatedAt = data.updatedAt
//json값 들고오는 법 공부
if(!data) {
// <req.body에 userId와 password가 제대로 저장되어 있지 않은 경우>
res.status(404).json({"data" : null, "message" : "not authorized"}).end() //에러 메세지를 보내준다
} else {
// <req.body에 userId와 password가 제대로 저장되어 있는 경우>
const requiredData = { //token을 생성할 때 header가 되는 부분이다
"id" : data.id,
"userId" : data.userId,
"email" : data.email,
"createdAt" : data.createdAt,
"updatedAt" : data.updatedAt
}
const accesstoken = jwt.sign(requiredData, process.env.ACCESS_SECRET, {expiresIn: "30m"}); //token의 signitur부분은 {algorithm(어떤 방식으로 암호화 할건지 결정함), issuer(토큰발행자 토큰 발행하는 client의 이름을 넣어주면 됨), expireIn(만료일자를 적어줌)}로 구성된다
const refreshtoken = jwt.sign(requiredData, process.env.REFRESH_SECRET, {expiresIn: "30m"})
res.cookie("refreshToken", refreshtoken) // cookie에 refreshtoken을 넣어준다
res.status(200).json({"data" : {"accessToken" : accesstoken}, "message" : "ok"}).end() //client에게 ok메세지와 함께 accesstoken을 보내준다
}
};
const { Users } = require('../../models');
const {verify, sign} = require("jsonwebtoken");
require("dotenv").config();
module.exports = (req, res) => {
//access token이 만료되서 refresh token으로 새로운 access token을 발급받고, client가 요청한 정보를 반환하는 모듈이다.
//여기서는 client의 cookie에 refresh token이 존재하는지 안하는지 여부에 따라 분기를 나눠서 작성해주자
//여기서 우선 client의 cookie에 refresh token이 존재하는지 확인한다. 존재하지 않는다면 {"data" : null, "message" : "refresh token not provided"}
//만약 client의 cookiedp refresh token이 존재하지만 비밀 키로 해독이 가능한지 안한지 여부에 따라 분기를 나눠 작성해준다.
//비밀 키로 해독이 가능한 것이 확인되지 않는다면 {"data" : null, "message" : "invalid refresh token, please log in again"}을 반환한다.
//비밀 키로 해독이 가능한 것이 확인된다면 필요한 정보들(id, userId, email, createdAt, updatedAt을 payload)에 담아 JWT token을 생성한다
const isRefreshToken = req.cookies.refreshToken;
//console.log(isRefreshToken)와 textcode를 통해 req.cookies.refreshToken가 3가지 상태를 가지고 있음을 알 수 있다.
//undefined : refreshToken이 존재하지 않음
//invalidtoken : refreshToken이 존재하지만 만료됨
//dalsjfblajsfblejnfla : 이렇게 생긴 이상한 문자열, refreshToken이 정상적으로 존재함
if(isRefreshToken) {
// <refresh token이 정상적으로 존재하는 경우>
if(isRefreshToken === 'invalidtoken') {
// <refresh token이 존재하지만 invalidtoken으로써 존재하는 경우>
return res.json({data : null, message : "invalid refresh token, please log in again"}).end() //토큰을 재발급 받으라고 메세지를 보내준다
}
// <refresh token이 존재하고 정상적으로 있는 경우>
const draft = verify(isRefreshToken, process.env.REFRESH_SECRET); //refresh token을 REFRESH_SECRET을 이용해 암호화를 풀어주고 정상인지 확인한다
const requiredData = { //client가 request한 cookies안에 token을 해독해서 얻어낸 draft에서 얻어낸 데이터들 이것을 다시 ACCESS key로 암호화 해서 client에게 보내줄 거임
"id" : draft.id,
"userId" : draft.userId,
"email" : draft.email,
"createdAt" : draft.createdAt,
"updatedAt" : draft.updatedAt
}
const accesstoken = sign(requiredData, process.env.ACCESS_SECRET, {expiresIn: "30m"}); //데이터를 다시 ACCESS key로 암호화 해준다
return res.json({data : {userInfo: requiredData, accessToken : accesstoken}, "message" :"ok"}).end() //response로 client에게 전달해준다
} else {
// <refresh token이 정상적으로 존재하지 않는 경우>
return res.json({data : null, message : "refresh token not provided"}).end() //client에게 refresh token이 존재하지 않는다고 메세지를 전달해준다
}
};
언제쯤 주석이 없어질까🥲