-
[Node.js] Express와 jwt 토큰(Json Web Token)을 이용한 회원가입 / 로그인 구현하기 [1]Backend 2019. 9. 11. 23:57
https://im-developer.tistory.com/164
지난번에 포스팅으로 올렸던 Express와 React 연습 앱을 활용해서
회원가입 / 로그인 구현을 해보기로 했다.
여러가지 방법들이 있겠지만 나는
지난번 바닐라코딩 서버 관련 과제를 하다가 만난 jwt 토큰을 이용해보기로 했다.
(Json Web Token이 무엇인지 자세히 알고 싶다면 아래 링크를 참고하자.)
https://auth0.com/learn/json-web-tokens/
아무튼 여기저기 검색을 해본 결과 jwt를 로그인에 이용하는 여러 방법 중에 가장 쉬워보였던 방법을 아래 적어보았다.
그럼 로그인을 구현하려면 일단 회원가입부터 구현해야하고,
그에 앞서서 일단 DB를 연결해야한다.
지난 과제에서 써봤던 MongoDB를 연결하기로 했다.
backend/app.js
const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/todo', { useNewUrlParser: true, useFindAndModify: false }); const db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function () { console.log('DB connected!!!'); });
generate express로 만들었던 backend/app.js 파일에
위 코드를 추가하여 MongoDB와 연결한다.
로그인/회원가입 끝내고 시간 남으면 todo list를 만들 생각이라
DB이름을 todo로 해주었다.
backend/models/User.js
const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ user_id: String, user_name: String, user_pw: String }); module.exports = mongoose.model('User', userSchema);
이제 DB에 저장할 회원 정보 스키마를 작성한다.
데이터 스키마를 작성한다는 것은 데이터의 틀을 미리 만들어두는 것이다.
그러니까 회원 정보에 어떤 속성들을 저장할 것인지,
data type은 어떤 것으로 할 것인지를 지정한다.
backend/app.js
app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/todos', todosRouter);
회원과 관련된 모든 요청은 '/users'로,
그리고 todo와 관련된 모든 요청은 '/todos'로 경로를 지정할 것이다.
backend/routes/users.js
const express = require('express'); const router = express.Router(); const usersController = require('./controllers/users.controllers'); router.post('/login', usersController.createToken); router.post('/new', usersController.createNewUser); module.exports = router;
user 정보와 관련된 요청들은 모두 POST 요청들이다.
login을 할 때 token을 새로 생성하는 것도 POST 요청이고,
새로운 user 정보를 db에 저장하는 것도 POST 요청이다.
GET와 POST 요청의 차이점이 궁금하다면 아래 링크를 참고하기 바란다.
https://im-developer.tistory.com/166
.env
SECRET_KEY=mySuperSecretKey
jwt를 생성할 때 사용하는 secret key는 환경변수로 따로 빼둔다.
(환경 변수란 주로 코드 안에 넣을 수 없는 중요하고 민감한 정보들을
코드와 상관없이 외부에 저장하여 별도로 관리하기 위해 사용한다.)
https://www.twilio.com/blog/2017/08/working-with-environment-variables-in-node-js.html
backend/routes/controllers/users.controllers.js
const User = require('../../models/User'); const jwt = require('jsonwebtoken'); require('dotenv').config(); const YOUR_SECRET_KEY = process.env.SECRET_KEY; exports.createToken = async function (req, res, next) { try { const user = await User.find(req.body); if (user.length) { const token = jwt.sign({ user_id: user[0].user_id }, YOUR_SECRET_KEY, { expiresIn: '1h' }); res.cookie('user', token); res.status(201).json({ result: 'ok', token }); } else { res.status(400).json({ error: 'invalid user' }); } } catch (err) { console.error(err); next(err); } }; exports.createNewUser = async function (req, res, next) { try { const user = await new User(req.body).save(); res.status(201).json({ result: 'ok', user: user }); } catch (err) { console.error(err); next(err); } };
token을 생성하는 createToken 메소드는
req.body로 전달되는 id와 pw 정보를 이용하여 user DB를 검색하고
해당 user가 있는 경우에 jwt를 새로 생성하여 쿠키에 저장하고, 201 응답을 반환한다.
만약에 userDB에 존재하지 않는다면 'invalid user'라는 에러 메시지와 함께 400 에러를 반환한다.
토큰을 만들 때 주의할 점은 jwt는 그냥 인코딩을 해줄 뿐, 암호화를 하는 것이 아니라는 점이다.
그렇기 때문에 jwt에 넣을 사용자 정보는 절대 비밀번호나 주민번호와 같은
민감한 개인정보를 담고 있어서는 안된다.
새로운 user를 생성하는 createNewUser 메소드는
req.body로 전달되는 정보를 DB에 저장하고 201 응답을 보낸다.
backend/routes/middlewares/authorization.js
const jwt = require('jsonwebtoken'); const YOUR_SECRET_KEY = process.env.SECRET_KEY; require('dotenv').config(); const verifyToken = (req, res, next) => { try { const clientToken = req.cookies.user; const decoded = jwt.verify(clientToken, YOUR_SECRET_KEY); if (decoded) { res.locals.userId = decoded.user_id; next(); } else { res.status(401).json({ error: 'unauthorized' }); } } catch (err) { res.status(401).json({ error: 'token expired' }); } }; exports.verifyToken = verifyToken;
로그인을 하고 난 후 보내는 API 요청마다 쿠키 정보를 검사하여
쿠키에 있는 jwt가 유효한지 검사해야한다.
jwt가 유효한지 확인할 때는 jwt.verify() 메소드를 사용한다.
만약에 jwt가 유효하다면 jwt가 디코딩되어 사용자 정보가 담긴 객체가 반환될 것이고
jwt가 유효하지 않거나 expired 되었다면 에러가 발생할 것이다.
만약에 jwt가 유효하여 사용자 정보가 담긴 객체를 반환받았다면
객체에 든 내용은 res.locals에 저장하여 다음에 호출될 함수에 값을 전달한다.
backend/routes/todos.js
const express = require('express'); const router = express.Router(); const todosController = require('./controllers/todos.controllers'); const { verifyToken } = require('./middlewares/authorization'); router.get('/', verifyToken, todosController.getAll); module.exports = router;
이제 todo 리스트 정보를 GET하는 요청이 client로부터 왔다면
위와 같이 처리한다.
먼저 아까 만들어둔 verifyToken 미들웨어를 거쳐서
token이 유효한지 확인한다.
token이 유효하다면 todosController.getAll을 호출한다.
backend/routes/controllers/todos.controllers.js
const Todo = require('../../models/Todo'); exports.getAll = async function (req, res, next) { try { const allTodos = await Todo.find({ user_id: res.locals.userId }); res.json({ todos: allTodos }); } catch (err) { next(err); } };
todo 요청을 받았을 경우 아까 미들웨어 함수에서 res.locals에 저장했던
user_id에 해당하는 todo 리스트를 찾아서 response로 보낸다.
이제 아주 기본적인 회원가입 / 로그인에 필요한 서버는 구축했고,
이제 클라이언트 쪽에서 적절하게 구현하면 된다!
반응형'Backend' 카테고리의 다른 글
[Node.js] 웹 소켓으로 실시간 랜덤 채팅 구현 중 메시지 중복 버그 해결과정 (WebSocket Random Chat - clients rendering duplicate message) (1684) 2019.10.05 [Node.js] Express와 jwt 토큰(Json Web Token)을 이용한 회원가입 / 로그인 구현하기 [2] (609) 2019.09.13 [Node.js] Express를 backend로 하는 Create-react-app 시작하기 (609) 2019.09.05 [C언어/Day3] C언어 수업정리 - 배열 기초 (정보처리기사, 실기) (0) 2019.05.22 [C언어/Day2] C언어 수업정리 (정보처리기사, 실기) / 자바스크립트와 산술연산 결과가 다르게 나오는 이유 (0) 2019.05.13 COMMENT