인증(Authentication), 인가(Authorization)

윤태규·2023년 12월 27일

01. 인증, 인가 살펴보기

  • 1) 인증(Authentication)
    👉 인증(Authentication)은 서비스를 이용하려는 사용자가 인증된 신분을 가진 사람이 맞는지 검증하는 작업을 뜻합니다. 일반적으로, 신분증 검사 작업에 해당합니다.
  • 인증(Authentication)은 일반적인 사이트의 로그인 기능에 해당합니다.
  • 로그인 기능은 일반적으로 id, password의 조합으로 이루어지겠죠?
    2) 인가(Authorization)
    👉 인가(Authorization)는 이미 인증된 사용자가 특정 리소스에 접근하거나 특정 작업을 수행할 수 있는 권한이 있는지를 검증하는 작업을 뜻합니다. 놀이공원에서 자유 이용권을 소지하고있는지 확인하는 단계라고 보면 좋습니다.
  • 인증된 사용자 즉, 로그인 된 사용자만 게시글을 작성할 수 있는지 검증한다면, 이 과정을 인가(Authorization) 과정이라고 부른답니다.
  • 인가(Authorization)기능은 사용자 인증 미들웨어를 통해서 구현할 예정입니다.

[게시판 프로젝트] 로그인, 회원가입 API

  • 1) [게시판 프로젝트] express 시작하기

    📌 이번 섹션은 게시판 프로젝트로그인, 회원가입기능을 구현할 예정입니다.

    • 📚  [게시판 프로젝트] API 명세서

      [ Prisma 게시판 프로젝트 API 명세서](https://www.notion.so/d9df8786b67740598af3d47aea4c38fb?pvs=21)

      Express.js를 이용한 게시판 프로젝트를 시작하기 전에, 먼저 app.js에서 기본적인 토대를 만들고 간단한 API를 구현해보겠습니다. 또한, 모든 Router에서 Prisma를 사용할 수 있도록 utils/prisma/index.js 파일을 생성하겠습니다. 😎

    • [코드스니펫][게시판 프로젝트] app.js 초기화

      // src/app.js
      
      import express from 'express';
      import cookieParser from 'cookie-parser';
      
      const app = express();
      const PORT = 3018;
      
      app.use(express.json());
      app.use(cookieParser());
      app.use('/api', []);
      
      app.listen(PORT, () => {
        console.log(PORT, '포트로 서버가 열렸어요!');
      });
    • [코드스니펫][게시판 프로젝트] Prisma 초기화

      // src/utils/prisma/index.js
      
      import { PrismaClient } from '@prisma/client';
      
      export const prisma = new PrismaClient({
        // Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
        log: ['query', 'info', 'warn', 'error'],
      
        // 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
        errorFormat: 'pretty',
      }); // PrismaClient 인스턴스를 생성합니다.
    • 📒 [Directory Structure][게시판 프로젝트] 프로젝트 초기화

      ```jsx
      .
      ├── package.json
      ├── prisma
      │   └── schema.prisma
      ├── src
      │   ├── app.js
      │   └── utils
      │       └── prisma
      │           └── index.js
      └── yarn.lock
      ```

      2) [게시판 프로젝트] 회원가입 API
      // app.js

import express from 'express';
import cookieParser from 'cookie-parser';
import UsersRouter from './routes/users.router.js';

const app = express();
const PORT = 3018;

app.use(express.json());
app.use(cookieParser());
app.use('/api', [UsersRouter]);

app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});

  • 3) [게시판 프로젝트] bcrypt

    👉 일반적으로, 사용자의 비밀번호를 데이터베이스에 저장할 때, 보안을 위해 비밀번호를 평문으로 저장하지 않고 암호화 하여 저장합니다.
    
    → [개인정보보호법](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EB%B3%B4%ED%98%B8%EB%B2%95)에서는 주민등록번호와 같은 정보는 암호화되어 저장되어야합니다.

    4) [게시판 프로젝트] 회원가입 API Bcrypt 리팩토링
    // src/routes/users.router.js

import bcrypt from 'bcrypt';

/ 사용자 회원가입 API 리팩토링/
router.post('/sign-up', async (req, res, next) => {
const { email, password, name, age, gender, profileImage } = req.body;
const isExistUser = await prisma.users.findFirst({
where: {
email,
},
});

if (isExistUser) {
return res.status(409).json({ message: '이미 존재하는 이메일입니다.' });
}

// 사용자 비밀번호를 암호화합니다.
const hashedPassword = await bcrypt.hash(password, 10);

// Users 테이블에 사용자를 추가합니다.
const user = await prisma.users.create({
data: {
email,
password: hashedPassword, // 암호화된 비밀번호를 저장합니다.
},
});

// UserInfos 테이블에 사용자 정보를 추가합니다.
const userInfo = await prisma.userInfos.create({
data: {
UserId: user.userId, // 생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
name,
age,
gender: gender.toUpperCase(), // 성별을 대문자로 변환합니다.
profileImage,
},
});

return res.status(201).json({ message: '회원가입이 완료되었습니다.' });
});
5) [게시판 프로젝트] 로그인 API, 사용자 인증 미들웨어
// src/routes/users.route.js

import jwt from 'jsonwebtoken';

/ 로그인 API /
router.post('/sign-in', async (req, res, next) => {
const { email, password } = req.body;
const user = await prisma.users.findFirst({ where: { email } });

if (!user)
return res.status(401).json({ message: '존재하지 않는 이메일입니다.' });
// 입력받은 사용자의 비밀번호와 데이터베이스에 저장된 비밀번호를 비교합니다.
else if (!(await bcrypt.compare(password, user.password)))
return res.status(401).json({ message: '비밀번호가 일치하지 않습니다.' });

// 로그인에 성공하면, 사용자의 userId를 바탕으로 토큰을 생성합니다.
const token = jwt.sign(
{
userId: user.userId,
},
'customized_secret_key',
);

// authotization 쿠키에 Berer 토큰 형식으로 JWT를 저장합니다.
res.cookie('authorization', Bearer ${token});
return res.status(200).json({ message: '로그인 성공' });
});
6) [게시판 프로젝트] 사용자 인증 미들웨어
// src/middlewares/auth.middleware.js

import jwt from 'jsonwebtoken';
import { prisma } from '../utils/prisma/index.js';

export default async function (req, res, next) {
try {
const { authorization } = req.cookies;
if (!authorization) throw new Error('토큰이 존재하지 않습니다.');

const [tokenType, token] = authorization.split(' ');

if (tokenType !== 'Bearer')
  throw new Error('토큰 타입이 일치하지 않습니다.');

const decodedToken = jwt.verify(token, 'customized_secret_key');
const userId = decodedToken.userId;

const user = await prisma.users.findFirst({
  where: { userId: +userId },
});
if (!user) {
  res.clearCookie('authorization');
  throw new Error('토큰 사용자가 존재하지 않습니다.');
}

// req.user에 사용자 정보를 저장합니다.
req.user = user;

next();

} catch (error) {
res.clearCookie('authorization');

// 토큰이 만료되었거나, 조작되었을 때, 에러 메시지를 다르게 출력합니다.
switch (error.name) {
  case 'TokenExpiredError':
    return res.status(401).json({ message: '토큰이 만료되었습니다.' });
  case 'JsonWebTokenError':
    return res.status(401).json({ message: '토큰이 조작되었습니다.' });
  default:
    return res
      .status(401)
      .json({ message: error.message ?? '비정상적인 요청입니다.' });
}

}
}
7) [게시판 프로젝트] 사용자 정보 조회 API
// src/routes/users.route.js

/ 사용자 조회 API /
router.get('/users', authMiddleware, async (req, res, next) => {
const { userId } = req.user;

const user = await prisma.users.findFirst({
where: { userId: +userId },
select: {
userId: true,
email: true,
createdAt: true,
updatedAt: true,
UserInfos: {
// 1:1 관계를 맺고있는 UserInfos 테이블을 조회합니다.
select: {
name: true,
age: true,
gender: true,
profileImage: true,
},
},
},
});

return res.status(200).json({ data: user });
});

profile
끝까지 가자

0개의 댓글