나의 발자취
[Node.js] JWT 인증 개념 및 활용 본문
RESTful API 인증
가장 간단한 인증 방식
클라이언트가 인증을 위해 사용자 이름과 비밀번호 인코딩
동작 원리
1. 클라이언트가 jttp 요청 헤더에 auth 추가..
토큰 인증 (오늘 구현할 것)
클라이언트가 서버에서 발급받은 토큰을 이용하여 인증
토큰은 주로 JSON Web Token(JWT) 형식
OAuth 2.0 (애플, 카카오 로그인 등)
인증 및 권한 부여 프레임워크
클라이언트가 다른 서비스의 리소스에 접근할 수 있는 권한을 부여하는 방식
주로 소셜 로그인, 서드 파티 api 접근 등에 사용
API 키 인증
공개 비공개 키를 사용하여 api 요청
주로 서드 파티 애플리케이션에서 사용
클라이언트가 api 키를 헤더나 쿼리 매개변수로 전송
프로젝트 시작
2024.10.17 - [Backend] - [Node.js] ORM sequelizer, Postgres 사용해서 MVC 패턴 적용해보기
[Node.js] ORM sequelizer, Postgres 사용해서 MVC 패턴 적용해보기
지난 포스팅2024.10.17 - [Backend] - [Node.js] 시퀄라이즈 게시판 첨부파일 기능 구현하기 [Node.js] 시퀄라이즈 게시판 첨부파일 기능 구현하기지난 포스팅2024.10.16 - [Backend] - [Node.js] sequelize-CLI 기존 테
wildguess.tistory.com
이전 실습에서 이어서 계속된다.
깃허브 레포지토리는 여길 참고: (히스토리)
https://github.com/est22/backend/commits/main/nodejs/10/ch10_03
DAO
dao > userDao.js 파일을 생성한다.
const models = require("../models");
// 회원가입
const createUser = async (data) => {
return await models.User.create(data);
};
const findAllUsers = async () => {
return await models.User.findAll();
};
const updateUser = async (id, data) => {
return await models.User.update(data, {
where: { id },
});
};
// 로그인
const findUserByEmail = async (email) => {
return await models.User.findOne({
where: { email },
});
};
module.exports = {
createUser,
findAllUsers,
updateUser,
findUserByEmail
}
Services
services > userService.js 생성
const userDao = require("../dao/userDao");
const createUser = async (data) => {
return await userDao.createUser(data);
}
const findUserByEmail = async (email) => {
return await userDao.findUserByEmail(email);
}
module.exports = {
createUser,
findUserByEmail
}
토큰 인증 관련 패키지 설치
npm i bcryptjs jsonwebtoken
- bcryptjs: 비밀번호를 안전하게 해싱하는 데 사용되는 라이브러리. 사용자의 비밀번호를 해싱하여 데이터베이스에 저장하고, 로그인 시 입력된 비밀번호와 해시된 비밀번호를 비교하는 데 유용
- jsonwebtoken: JSON 웹 토큰(JWT)을 생성하고 검증하는 데 사용되는 라이브러리. 사용자 인증 및 권한 부여를 위해 클라이언트와 서버 간의 정보를 안전하게 전송하는 데 사용. JWT는 일반적으로 로그인 후 사용자 정보를 포함하여 클라이언트에 발급
controllers > authController.js
const bcrypt = require("bcryptjs");
const userService = require("../services/userService");
const { createUser } = require("../dao/userDao");
const register = async (req, res) => {
const { email, name, password } = req.body;
// password : admin1234
const hashedPassword = await bcrypt.hash(password, 10);
try {
const user = await userService.createUser({
email: email,
name: name,
password: hashedPassword,
});
res.status(201).json({ data: user });
} catch (e) {
res.status(500).json({ error: e.message });
}
};
module.exports = {
register
};
여기서, 10이 의미하는 것은 salt 의 cost factor로, 문서의 복잡도를 조정해주는 인자값이고 10이 제일 권장되는 수치이다.
routes > authRoute.js
const express = require("express");
const { register } = require("../controllers/authController");
const router = express.Router();
router.post("/register", register);
module.exports = router;
app.js 에 라우터를 추가해준다. (line 3, line 10)
그러면 http://localhost:3000/auth/register 로 POST 요청을 하면 된다.
POST register test
그리고 포스트맨에서 POST 테스트를 해주면 아래와 같이 유저의 비밀번호는 솔트 처리+해싱이 되어 저장이 되는 것이 확인된다.
로그인 구현
최상위 디렉토리에 utils 라는 폴더를 만들어준다.
그 안에 token.js를 생성해준다. JWT를 구현해줄 것이다.
const jwt = require("jsonwebtoken");
// 사용자 인증을 위한 용도
const generateAccessToken = (user) => {
return jwt.sign(
{
id: user.id,
email: user.email,
},
"access_secret",
{ expiresIn: "15m" }
);
};
// accessToken을 발급받기 위한 용도
const generateRefreshToken = (user) => {
return jwt.sign(
{
id: user.id,
email: user.email,
},
"refresh_secret",
{ expiresIn: "14d" }
);
};
module.exports = {
generateAccessToken,
generateRefreshToken
}
authController.js 에 아래의 코드를 추가한다.
const { generateAccessToken, generateRefreshToken } = require("../utils/token");
const login = async (req, res) => {
const { email, password } = req.body;
try {
const user = await userService.findUserByEmail(email);
if (!user) {
return res.status(400).json({ message: "Invalid email or password" });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: "Invalid email or password" });
}
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
res.json({
accessToken,
refreshToken
})
} catch (e) {
res.status(500).json({ error: e.message });
}
};
마지막에 모듈 export를 해준다.
그리고 다시 authRoute.js 로 가서, 새로 구현한 login 모듈 추가를 해준다.
POST login test
그리고 로그인 테스트를 해준다!
아래와 같이 성공적으로 accessToken, refreshToken이 나오는 것을 볼 수 있다.
미들웨어 생성
로그인을 해야지만 글쓰기가 가능한 미들웨어를 만들어볼 것이다.
최상위 디렉토리에 middleware 폴더를 새로 생성하고, auth_middleware.js 파일을 새로 생성한다.
여기서의 next는 요청 처리를 마친 후 다음으로 할 활동에 대한 인자이다.
예를 들어, 헤더에는 'Authorization: Bearer ei2EwaidfjkJD2.3423' 라는 정보가 들어온다고 하자. 그러면 토큰은 Bearer 뒤의 ei2EwaidfjkJD2.3423 가 될것이다. 여기서 split을 하게 되면 배열이 나오게 된다.
위를 예시로 들면, ['Bearer', 'ei2EwaidfjkJD2.3423'] 이라는 배열이 나올것이고 [1]을 선택하면 원하는 값인 'ei2EwaidfjkJD2.3423'만 딱 가지고 올 수 있다.
auth_middleware.js
const jwt = require("jsonwebtoken");
const authenticateToken = (req, res, next) => {
let token;
if (req.headers.authorization) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) {
res.status(401);
}
jwt.verify(token, "access_token", (err, user) => {
if (err) return res.status(401);
req.user = user;
next(); // 다음 번 미들웨어로 제어가 넘어간다. 다음번 미들웨어가 없으면 이 미들웨어를 장착한 라우터 실행
});
};
module.exports = {
authenticateToken,
};
그리고 postRoute.js에 가서 라우터를 추가해준다.
로그인 후 포스트 작성 테스트
포스트맨에 가서 헤더에 새로운 헤더를 추가한다.
key: Authorization, value: 아까 로그인 요청을 했을 때 나온 "accessToken" 값을, Bearer 뒤에 띄고 붙여넣어서 요청을 해준다. (나는 혹시나 새로 리프레쉬를 해서 새 값을 받았다.)
아래 값을 넣으면
이렇게 정상적으로 포스팅을 쓸 수 있다!
![](https://blog.kakaocdn.net/dn/qzykB/btsKbDlzgAH/5JREi1iRkc7xJcxW6HNoCk/img.png)
로그인한 userId 매칭하기
이제, 유저가 로그인을 해야지만 포스트를 발행할 수 있도록 해주는 작업이 완료되었다.
그런데, 방금 테스트에서의 요청내역처럼 로그인을 할 때 userId도 값을 집어넣어줘야했었다.
이제 userId를 입력하지 않고도 유저를 내부적으로 매칭해서 해당 유저의 DB에 포스팅을 작성하도록 하는 작업을 해줄 것이다.
postController.js 에 가서 createPost()에 아래로 업데이트
const createPost = async (req, res) => {
// 게시글 작성
try {
// {"title":"a","content","b","userId":2} = req.body
const user = req.user;
const post = await postService.createPost({
title: req.body.title,
content: req.body.content,
userId: user.id,
});
res.status(201).json({ data: post });
} catch (e) {
res.status(500).json({ error: e.message });
}
};
전 코드와 비교해보면, req.body 부분에다가 객체를 보내는 것을 실었다.
이렇게 되면 userId가 없이도 로그인한 userId를 내부적으로 매칭해서 포스팅을 작성할 수 있는것이다!
토큰 Refresh 기능
현재 로그인 토큰 기간이 15분이므로, 리프레쉬를 해주는 작업을 할 것이다.
authController.js 가서 아래와 같이 refresh 콜백함수를 만들어준다. 이렇게 되면
모듈을 export 시킨다
authRoute.js 에 가서 객체 안에 refresh 를 넣어주고, 라우터 작업도 해준다.
http://localhost:3000/auth/refresh 에 가서 refreshToken을 넣고 호출을 하게 되면 accessToken이 새로 발급될 것이다.
일단 아래와 같이 로그인 요청을 했을 때,
위에 나오는 refreshToken값을 복사해서
POST/ http://localhost:3000/auth/refresh 주소로 요청을 해본다.
그러면 아래와 같이 accessToken이 새로 발급된다!
이제까지 한 토큰 인증 과정들은 이와 같다.
'Backend' 카테고리의 다른 글
Docker에 대한 기본 개념 정리, Docker 기본 명령어 정리 (2) | 2024.11.01 |
---|---|
[Node.js] MongoDB, Mongoose 활용해서 CRUD 기능 게시판 만들기 (2) | 2024.10.18 |
[Node.js] ORM sequelizer, Postgres 사용해서 MVC 패턴 적용해보기 (4) | 2024.10.17 |
[Node.js] 시퀄라이즈 게시판 첨부파일 기능 구현하기 (3) | 2024.10.17 |
[Node.js] sequelize-CLI 기존 테이블 필드 추가, 외래키 association 적용 (1) | 2024.10.16 |