인증 및 인가

이찬영·2024년 11월 10일

이번엔 프로젝트에 회원가입 / 로그인 기능을 구현해보았다.

JWT

JWT(Json Web Token)은 웹 애플리케이션에서 사용자를 인증하고 정보를 안전하게 전달하는 데 사용되는 표준화된 토큰이다. JWT는 사용자 인증을 위해 사용되며, 인증된 사용자가 요청할 때마다 유효한 토큰을 제공함으로써 인증상태를 유지할 수 있게 한다.

JWT구조

JWT는 점(.)으로 구분된 세가지 파트로 이루어져 있다.
1. 헤더 (Header) : 토큰 타입 (JWT) 과 해싱 알고리즘에 대한 정보를 포함한다.
2. 페이로드 (Payload) : 주로 사용자 정보를 포함하며, 클레임이라고도 불린다. 예를들어, 사용자ID, 역할 정보 등이 들어갈 수 있다.
3. 서명 (Signature) : 비밀키를 이용해 헤더와 페이로드를 합쳐 생성한 서명으로, 토큰이 변조되지 않았음을 보장한다.

  • JWT의 예시 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJ0ZXN0dXNlciIsImlhdCI6MTYwMjU5NTI1OX0.dQw4w9WgXcQ7ubmYkYub1RR1osfNTuCqK2zR1LpqD7E

Node.js에서는 jsonwebtoken라이브러리를 사용해 JWT를 쉽게 생성하고 검증할 수 있다.

  1. 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시간으로 설정한다.

  1. JWT검증 (verify)
    클라이언트로부터 받은 토큰을 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

bcrypt는 비밀번호를 안전하게 해싱하기 위해 사용되는 해싱 알고리즘인데, salt라는 무작위 데이터가 추가되어, 같은 비밀번호라도 매번 다른 해시값이 생성되도록 하여 보안을 강화한다.

비밀번호 저장 및 검증에서 주로 사용되며,
bcrypt.hash(password, 횟수)로 해시 생성
bcrypt.compare(password, 암호화된 password)로 검증

할 수 있다.

0개의 댓글