이번엔 프로젝트에 회원가입 / 로그인 기능을 구현해보았다.
JWT(Json Web Token)은 웹 애플리케이션에서 사용자를 인증하고 정보를 안전하게 전달하는 데 사용되는 표준화된 토큰이다. JWT는 사용자 인증을 위해 사용되며, 인증된 사용자가 요청할 때마다 유효한 토큰을 제공함으로써 인증상태를 유지할 수 있게 한다.
JWT는 점(.)으로 구분된 세가지 파트로 이루어져 있다.
1.헤더 (Header): 토큰 타입 (JWT) 과 해싱 알고리즘에 대한 정보를 포함한다.
2.페이로드 (Payload): 주로 사용자 정보를 포함하며, 클레임이라고도 불린다. 예를들어, 사용자ID, 역할 정보 등이 들어갈 수 있다.
3.서명 (Signature): 비밀키를 이용해 헤더와 페이로드를 합쳐 생성한 서명으로, 토큰이 변조되지 않았음을 보장한다.
JWT의 예시 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJ0ZXN0dXNlciIsImlhdCI6MTYwMjU5NTI1OX0.dQw4w9WgXcQ7ubmYkYub1RR1osfNTuCqK2zR1LpqD7E
Node.js에서는 jsonwebtoken라이브러리를 사용해 JWT를 쉽게 생성하고 검증할 수 있다.
JWT 생성(Sign)jwt.sign() 함수를 사용하여 JWT를 생성할 수 있다.import jwt from 'jsonwebtoken';
const JWT_SECRET = 'mySecretKey'; // 실제로는 안전하게 환경변수로 설정할것
// 사용자 정보 (실제로는 데이터베이스)
const user = {
id : 1,
name : 'user1'
};
// 토큰생성
const token = jwt.sign({userId : id}, JWT_SECRET, {expiresIn : '1h'});
위 코드에서 {userId : id}는 토큰에 포함될 사용자 정보를 나타낸다. 페이로드에 사용자 정보를 넣으면 클라이언트가 사용자와 관련된 데이터를 안전하게 전송할 수 있다.
JWT_SECRET은 토큰을 암호화할때 사용하는 비밀키 이다.
{expiresIn : '1h'}는 토큰의 유효기간을 1h. 즉, 1시간으로 설정한다.
JWT검증 (verify)// 인증 미들웨어 (authmiddelware)
import jwt from 'jsonwebtoken';
import {prisma} from '../utils/prismaClient.js';
import dotenv from 'dotenv';
dotenv.config();
export default async function (req, res, next) {
try{
const {authorization} = req.headers; // 클라이언트에서 헤더에 담아서 요청
if(!authorization) {
return res.status(400).json({message : "authorization 헤더가 없습니다."});
const [tokenType, token] = authorization.split(' ');
const key = process.env.JWT_SECRET;
if(!token){
return res.status(400).json({message : "토큰이 존재하지 않습니다."});
if(tokenType !== 'Bearer') {
return res.status(400).json({message : "Token Type이 일치하지 않습니다."});
const verifyToken = jwt.verify(token, key);
const userId = verifyToken.userId;
const userInfo = await prisma.user.findUnique({
where : {id : id}
});
if(!userInfo) {
return res.status(400).json({message : "토큰 사용자가 존재하지 않습니다."});
}
req.user = userInfo
next();
}
}
}
}catch(err){
if(err.name === 'TokenExpriedError') {
return res.status(401).json({message : "Token이 만료되었습니다."});
} else if (err.name === 'JsonWebTokenError') {
return res.status(401).json({message : "유효하지 않은 Token입니다."});
} else {
return res.status(500).json({message : "서버 에러가 발생했습니다."});
}
}
}
이제 이 미들웨어로 사용자를 확인하고 인증할 수 있게 된다.
회원가입시 password가 필요할것인데, 이것은 평문으로 저장되는것보다, bcrypt와 같은 해싱 라이브러리를 사용해서 암호화된 password로 db에 저장하는 편이 보안성이 높다.
// 회원가입
router.post('/signup', async (req, res) => {
const {email, password} = req.body;
// 중복체크
const existingUser = await prisma.user.findFirst({
where : {id : id}
});
if(existingUser) {
return res.status(401).json({message : "이미 가입되어 있는 email입니다."});
}
// 비밀번호 해싱
const hashedPassword = await bcrypt.hash(password, 10);
// 사용자 저장
const createUser = await prisma.user.create({
data : {
email : email,
password : password
}
})
res.status(201).json({message : "회원가입이 완료되었습니다."});
});
// 로그인
// 로그인시, 입력된 비밀번호가 저장된 비밀번호 해시와 일치하는지 확인하고, 일치하면,
// JWT토큰 생성
router.post('/login', async (req, res) => {
const {email, password} = req.body;
const user = await prisma.user.findFirst({
where : {email : email}
});
const isPasswordValid = await bcrypt.compare(password, user.password);
if(!isPasswordValid) {
return res.status(400).json({message : "비밀번호가 일치하지 않습니다."});
}
// JWT생성
const token = jwt.sign({userId : user.id}, process.env.JWT_SECRET, {expiresIn : '1h'});
res.json({ token : `Bearer ${token}`, message : "로그인이 완료되었습니다." });
});
이제 라우트에 미들웨어만 추가해주면, 미들웨어를 통해 사용자의 id를 찾아내, 그 사용자의 권한 등을 관리할 수 있다.
bcrypt는 비밀번호를 안전하게 해싱하기 위해 사용되는 해싱 알고리즘인데, salt라는 무작위 데이터가 추가되어, 같은 비밀번호라도 매번 다른 해시값이 생성되도록 하여 보안을 강화한다.
비밀번호 저장 및 검증에서 주로 사용되며,
bcrypt.hash(password, 횟수)로 해시 생성
bcrypt.compare(password, 암호화된 password)로 검증
할 수 있다.