글이 잘 보이지 않는다면 우측 하단의 달 모양을 눌러 라이트모드로 변경해주시기 바랍니다.
해당 글은 MacOS 를 기준으로 작성이 되었음을 미리 알려드립니다. WindowOS 를 이용하시는 분은 추후 업로드될 게시글을 기다려주시거나 다른 작성자의 게시글을 확인해주시기 바랍니다.
JWT를 이용하는 나만의 이유
예전에는 Session 을 이용해 자동로그인, 인증등을 하고 있었습니다. 하지만 이 기능은 구현하기가 복잡하기도 하고 DB 혹은 서버에 많은양의 데이터를 보관/처리를 하기때문에 성능상 이슈가 발생하고 있었습니다.
이러한 이슈를 해결하기 위해서어떤 방법이 있을까 하다가 찾게 된것이 JWT 였습니다.
JWT 는 AccessToken과 RefreshToken 을 클라이언트에서 가지고 있다가 필요할때만 서버에 요청하여 인증을 받는 방식이었는데 해당 방식이 서버의 성능을 크게 망치지 않는다 생각하여 채택을 하게 되었습니다.
JWT Token 알아보기
Json Web Token 의 약자로,
HEADER , PLAYLOAD , VERIFY SIGNATURE 로 나뉩니다.
>> JWT 동작원리
최초 로그인은 아래와같은 동작원리로 되어있습니다.
이 후 자동 로그인을 할때는 아래의 동작이 이루어지게 됩니다.
JWT ( Json Web Token ) 설치 및 적용
yarn add jsonwebtoken
yarn add dotenv
위와같이 설치를 진행해줍니다.
이 후 원하는 곳에 jwt.js 를 만들어주고 코드를 작성해줍니다.
// jwt.js
require("dotenv").config();
const jwt = require('jsonwebtoken');
이렇게 작성을 해주셨다면, .env 파일을 만들어 아래와같이 작성을 해 줍니다.
// .env
ACCESS_TOKEN_SECRET="YOUR_ACCESS_KEY"
REFRESH_TOKEN_SECRET="YOUR_REFRESH_KEY"
이렇게까지 했으면 어느정도 준비 과정은 끝이났습니다.
이제 로그인 후 JWT 토큰 발급하는 과정을 해보겠습니다.
JWT 토큰발급
jwt.js 안에서 아래와같이 작성을 해주도록 하겠습니다.
// jwt.js
const token = () =>{
return{
access(id){
return jwt.sign({id}, process.env.ACCESS_TOKEN_SECRET , {
expiresIn: "15m",
});
},
refresh(id){
return jwt.sign({id}, process.env.REFRESH_TOKEN_SECRET , {
expiresIn: "180 days",
});
}
}
}
이 함수를 이용하여 최초 로그인을 했을 때 accessToken 과 refreshToken 을 발급해주는 부분 입니다.
굳이 이렇게 따로 분리한 이유는, 추후 accessToken 은 refreshToken 에 의해 새로 발급을 해주기위해서입니다.
이제 인증을 하는 부분을 만들어보도록 하겠습니다.
// auth.js
router.get('/user' , (req,res)=>{
})
우리가 흔히 알고있는 라우팅 방법은 이렇게 되어있습니다.
하지만 이제는 조금 다른 형식으로 하려고 합니다.
router.route('/user')
.get( (req, res)=>{} )
이런식으로 진행을 하고자 합니다.
여기서 ( req, res ) => {} 이 부분도 따로 떼어내 userLogin 으로 만들도록 하겠습니다.
exports.userLogin = (req,res) => {
res.send('userLogin');
}
이런 형식으로 따로 만들어주도록 합니다.
그리고 jwt.js 로 돌아가서
//jwt.js
exports.authenticate = (req, res, next) => {
if(req.query.id === 'hello'){ // id 가 일치할 때
req.authData = {
status : 200,
message : 'Correct User Data',
jwt:{
accessToken : token().access(req.query.id),
refreshToken : token().refresh(req.query.id)
}
};
}else{ // 일치하지 않을 때
req.authData = {
status : 400,
message : 'Not Correct User Data'
};
}
next();
}
아래와 같은 코드를 작성해주도록 합니다.
그리고 auth.js 로 돌아가 미들웨어를 추가로 작성해주도록 합니다.
//auth.js
const router = require('express').Router();
const user = require('../../controller/user')
const {authenticate} = require('../../lib/auth/jwt')
router.route('/user')
.get( authenticate , user.userLogin )
module.exports = router
이렇게 작성을 해 주시고 user.js 파일 안에서 마무리로 작업을 해 줍니다.
//user.js
exports.userLogin = (req,res) => {
const userData = req.authData;
if(userData.status === 200){
res.json(userData.jwt)
}else{
res.send('ERROR')
}
}
이렇게 하면 최초 로그인시 토큰 발급은 끝이나게 됩니다.
JWT 자동로그인
이제 자동 로그인을 해볼 차례 입니다.
위 순서를 보면 자동로그인 시 AccessToken 을 체크하게 됩니다.
//auth.js
router.route('/token')
.get(authenticateAccessToken, (req,res)=>{
console.log(req.user)
res.send(req.user)
})
위 코드를 보면 get 방식으로 보내게 되는데 AccessToken 을 쿼리에 담아서 보내면 많이 어려운 부분이 있기때문에 헤더에 담아서 보내도록 하겠습니다.
//jwt.js
const authenticateAccessToken = (req, res, next) => {
let authHeader = req.headers["authorization"];
let token = authHeader && authHeader.split(" ")[1];
if (!token) return res.sendStatus(400);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (error, user) => {
if (error) return res.sendStatus(403);
req.user = user;
next();
});
}
/auth/token 으로 요청을 보내게 되면 우선적으로 authenticateAccessToken 함수의 미들웨어로 접근을 하게 됩니다. 이 미들웨어 안에서는 헤더 안에 authoriztion 이라는 이름으로 보내주면 토큰과 아이디로 나누어 토큰의 유무와 검증을 하게 됩니다. 이 후 정상적인 결과를 가져오게 된다면 req.ueser 안에 user 정보를 담아 다음 미들웨어로 넘어가게 되는데 위 auth.js 를 보시면 req.ueser 안의 결과를 받아올 수 있게 됩니다.
코드를 잘 정리해주셧다면 이처럼 Header에 담아서 보낼건데 authorization 이름으로 "id accessToken" 순으로 넣어주세요. 가운데에 띄어쓰기는 옵션입니다.
이 안에 특수문자를 넣어서 대처할 수 있습니다.
expiresIn 에서 적용한 시간이 다 되었을 시 아래와 같이 뜨게되며
AccessToken 을 담아서 보내지 않으면 이와같이 나타나게 됩니다.
expiresIn 에서 적용한 시간이 다 되지 않고 정상적인 AccessToken 을 받아오게 된다면 이와같은 값을 보내주게 됩니다.
JWT 자동로그인 AccessToken 만료
위에서 만료가 되었을 시 403 Forbidden 오류를 뿜었습니다. 그럼 프론트에서는 이 오류를 받았을 때 refreshToken 과 함께 새로운 AccessToken 을 발급받을 수 있도록 해주면 되겠지요?
//auth.js
router.route('/token')
.get(authenticateAccessToken, (req,res)=>{
console.log(req.user)
res.send(req.user)
})
.post((req, res) => {
const refreshToken = req.body.refreshToken;
if(!refreshToken) return res.sendStatus(403);
const accessToken = token().issuance(refreshToken, res)
console.log(accessToken)
res.json({accessToken})
})
기존 경로에 post 형식으로 보낼 수 있도록 해주겠습니다.
한 경로에 post , get , put , delete 등 메서드별 기능을 다룰 수 있는 CRUD 에 대해서는 나중에 소개하도록 하겠습니다.
자 그럼 코드를 풀어보자면, 우선 body에 refreshToken 을 담아 서버로 보내주고, refreshToken을 보내주지 않았다면 403 에러를 뿜어주게 해줍니다. 하지만, 토큰이 있다면 token().issuance() 함수에 refreshToken 과 res 를 보내줍니다.
// jwt.js
const token = () =>{
return{
access(id){
return jwt.sign({id}, process.env.ACCESS_TOKEN_SECRET , {
expiresIn: "15m",
});
},
refresh(id){
return jwt.sign({id}, process.env.REFRESH_TOKEN_SECRET , {
expiresIn: "180 days",
});
},
issuance(token, res) {
return jwt.verify(
token,
process.env.REFRESH_TOKEN_SECRET,
(err, user) => {
if (err) res.sendStatus(403)
const key = this.access(user.id)
return key
}
)
}
}
}
클로저를 이용하여 이런식으로 함수를 선언해주도록 합니다.
issuance 함수에서 jwt.verify 를 통하여 refreshToken과 기존의 REFRESH_TOKEN_SECRET 을 이용해 유효성 검사를 해주고, 문제가 있다면 403 Forbidden 오류를, 없다면 새로운 AccessToken 을 발급해서 보내주도록 합니다.
이 후 프론트에서 로그인 처리를 해주면 되겠습니다.
'JavaScript > Nodejs' 카테고리의 다른 글
[NodeJS] TypeScript 사용 시 Request CustomType (0) | 2021.12.23 |
---|---|
[NodeJS] Mysql 연결하기 [Mysql createPool] (0) | 2021.09.04 |
[Nodejs] 로그인 / 회원가입 [ 로그인 ] (0) | 2021.08.14 |
[Nodejs] 로그인 / 회원가입 [ Mysql 세팅 및 연동 / express-mysql-session ] (0) | 2021.08.12 |
[Nodejs] 로그인/회원가입 구현하기 (세션유지편) (0) | 2021.05.21 |