본문 바로가기
개발/차근차근 개발일지 TIL

TIL, WIL 230423_RESTful API, Package.json, cookies, session, 주특기 2주차 과제 관련 오류&해결

by 코딩하는짱구 2023. 4. 24.
반응형

✅오늘 학습 Keyword

 Restful API, package.json, 쿠키, 세션, JWT

✅개념정리 

Restful API-Representational State Transfer API의 약자로, 웹 서비스와 애플리케이션 간에 데이터를 교환하기 위한 아키텍처 스타일, HTTP 메서드(GET,POST,PUT,DELETE), JSON, 상태를 유지하지않음(요청시에 모든 정보가 담겨있기 때문에 서버에서 별도의 상태 정보를 관리하지 않아도 됌)

 

package.json-Node.js 프로젝트에서 사용되는 파일, 프로젝트 이름, 버전, 설명등의 정보, 프로젝트에서 사용하는 패키지(모듈)들의 버전 정보 및 의존성(dependency)관리, 스크립트를 실행할 수 있는 명령어, npm init -y로 생성, 프로젝트 할때마다 생성 ㅇㅇ

 

쿠키-클라이언트와 서버간에 정보를 겨환할 수 있는 텍스트 파일, 로그인정보, 쇼핑카트에 추가된 정보, 클라이언트(브라우저)에 저장됌. 

 

세션-Session, 클라이언트와 서버간의 상태를 유지하기 위한 메커니즘, 쿠키와 비슷한 개념이지만 서버측에 저장됌. 

express-session 모듈을 사용하여 세션을 구현하면, 다음과 같은 기능을 제공합니다.

  • 세션 ID를 생성하고 클라이언트에게 쿠키로 전송합니다.
  • 세션 ID를 사용하여 서버 측에 세션 데이터를 저장합니다.
  • 세션 ID를 사용하여 클라이언트에서 전송된 요청과 서버 측에 저장된 세션 데이터를 연결합니다.
const express = require('express');
const session = require('express-session');

const app = express();

// 세션 미들웨어 등록
app.use(session({
  secret: 'my-secret-key', // 세션 암호화를 위한 비밀키
  resave: false,
  saveUninitialized: true,
}));

// 라우터 등록
app.get('/', function(req, res) {
  // 세션 데이터 설정
  req.session.name = 'John';

  // 세션 데이터 사용
  res.send(`Hello ${req.session.name}`);
});

app.listen(3000, function() {
  console.log('Server is running on port 3000');
});

 

JWT-JSON WEB TOKEN!! 클라이언트-서버간의 인증정보를 전송하기 위한 토큰 기반의 인증 시스템. 

구성은 Header, payload, signature 로 개미

✅오늘 겪은 문제

과제 lv2 routes.posts에서 로그인 후 게시글작성 기능 구현하기

-주특기 1주차 때 만든 게시판(게시글&댓글 작성, 조회, 수정, 삭제)에 회원가입 및 로그인 기능을 추가해야한다. 

-간단히 회원가입, 로그인 시스템만 만들면 된다고 생각했고 회원가입 후 로그인, 로그인시 cookies에 jwt값 생성까진 문제가 없었으나 .. 게시글을 작성하는 부분에서 계속 api가 안열리고, 아래 코드의 오류가 반복됐다. 

  const [authType, authToken] = (Authorization ?? '').split(' '); // Authorization이 존재하지 않으면 ''<빈 문자열 할당
  //즉 authorization이 undefined거나 null일경우 빈 문자열을 할당한다.
  //만약 'Bearer asldkfjasldfkjwelkfjasldfkjas;dfkjsa;fdksja;fkjsaljfasdf'이 authorization이라면,
  //authType = Bearer , authToken= asdlsdfsdfsdfsdflj
  console.log(authType, authToken);

  if (authType !== 'Bearer' || !authToken) {
    res.status(400).json({
      errorMessage: '토큰 타입이 일치하지 않거나, 토큰이 존재하지 않습니다.',
    });
    return;
  }

 

 

✅해결방법

1. 우선 api가 안열리는 문제는 경로의 문제였는데, 일단 기존에 지정해놓은 api/posts/뒤로 경로를 지정해서 해결했다.

하지만 아래는 일시적 해결 방법이고 내가 원하는 경로는 이게 아니기에 내일 좀 더 깔끔히 분리할 방법을 찾아봐야겠다. 

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 3000;
const postsRouter = require('./routes/posts.js');
const usersRouter = require('./routes/users.js');
const loginRouter = require('./routes/login.js');

// const commentsRouter = require("./routes/comments.js");
const connect = require('./schemas');
connect();

app.use(express.json()); //실제로 body에 data가 들어왔을때 들어온 body data를 사용하게 해주는 미들웨어
// app.use('/api', usersRouter);
app.use(cookieParser());
app.use('/api/posts', [postsRouter, usersRouter, loginRouter]); //전역 middleware로써 router를 등록한다
// app.use("/api/posts", commentsRouter);

app.get('/', (req, res) => {
  res.send('Hello World!');
});

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

 

 

2.  서버 문제는 해결했는데 routes.posts.js 에서 req.body를 지정해주는 곳에서 원래 있던 user변수가 선언되지 않고있다. 왜일까? 바로 사용자 인증 미들웨어를 통해 (무사히 로그인 된 상태에서) userId를 받아서 쓸 예정이므로 user는 그 전과 값이 같지 않기때문. 게시글을 반환할때 저장된 userId를 받아줄 새로운 항목이 필요하기에  post 의 schema부터 수정해줬다. 해결!

const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
  title: {
    type: String,
    required: true,
  },
  password: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
  comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
});

const Post = mongoose.model('Posts', postSchema);
module.exports = Post;

 

//게시글등록 //로그인 필요 //userId->게시글을 할당
router.post('/create', authMiddleware, async (req, res) => {
  const { title, password, content } = req.body;
  const { user } = res.locals; // 미들웨어에서 쿠키에서 추출한 user 정보를 가져옴

  try {
    // 게시글 생성
    const post = await Post.create({
      user: user._id, // 유저 정보를 post schema에 추가
      title,
      password,
      content,
    });

    res.status(201).json({ post });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      errorMessage: '게시글 작성 중 오류가 발생했습니다.',
    });
  }
});

 

3.

auth-middleware.js도 잘 작성 됐고 게시글 작성 api도 잘 작성했는데 왜 대체 왜 뭐가 문제일까 고민해봤다.

우선

A. authorization에 쿠키정보가 제대로 들어오는지 체크했을때, 쿠키 값의 authorization 이 아니라 Authorization이 맞는것으로 확인되서 바꿈.

B. Authorization으로 바꿨음에도 불구하고 정보가 안들어오길래 다시 보니 split함수에서 ''<공백을 지정하지 않았다..

아래와 같이 코드를 수정하니 무사히 게시글 작성기능 구현!

 
 
const jwt = require('jsonwebtoken');
const User = require('../schemas/user.js');

module.exports = async (req, res, next) => {
  console.log(req.cookies);

  const { Authorization } = req.cookies; // Authorization 쿠키에 jwt 토큰이 담겨있는지 확인

  if (!Authorization) {
    res.status(400).json({
      errorMessage: '로그인이 필요한 기능입니다.', //Authorization 자체가 전달되지 않았을경우, 즉 로그인이 안됌.
    });
    return;
  }

  const [authType, authToken] = (Authorization ?? '').split(' '); // Authorization이 존재하지 않으면 ''<빈 문자열 할당
  //즉 authorization이 undefined거나 null일경우 빈 문자열을 할당한다.
  //만약 'Bearer asldkfjasldfkjwelkfjasldfkjas;dfkjsa;fdksja;fkjsaljfasdf'이 authorization이라면,
  //authType = Bearer , authToken= asdlsdfsdfsdfsdflj
  console.log(authType, authToken);

  if (authType !== 'Bearer' || !authToken) {
    res.status(400).json({
      errorMessage: '토큰 타입이 일치하지 않거나, 토큰이 존재하지 않습니다.',
    });
    return;
  }

  //토큰이 유효하면
  try {
    //쿠키에서 JWT토큰을 가져온 후, JWT토큰에서 사용자 ID를 추출.
    const { userId } = jwt.verify(authToken, 'customized-secret-key'); //authToken 검증할 토큰, ''은 토큰을 생성할 때 사용된 비밀 키
    //jwt.verify()가 검증에 성공하면 해독된 JWT페이로드(payload)를 반환.
    //즉 userId라는 이름으로 반환된 페이로드의 userId값을 추출하여 변수에 할당. userId=토큰에 포함된 사용자 ID
    //추출된 사용자 ID를 사용하여 mongoDB의 'users'컬렉션에서 해당 사용자를 찾음
    const user = await User.findById(userId); //즉 user는 해당사용자

    if (!user) {
      // 유저 정보가 존재하지 않을 경우 에러 처리
      res.status(403).json({
        errorMessage: '로그인이 필요한 기능입니다.',
      });
      return;
    }

    res.locals.user = user; //user는 해당사용자
    console.log(user);
    next();
  } catch (error) {
    console.error(error);

    if (error.name === 'TokenExpiredError') {
      // 토큰이 만료된 경우 에러 처리
      res.status(403).json({
        errorMessage: '로그인 세션이 만료되었습니다. 다시 로그인해주세요.',
      });
    } else {
      // 그 외의 인증 에러인 경우 에러 처리
      res.status(403).json({
        errorMessage: '전달된 쿠키에서 오류가 발생하였습니다.',
      });
    }
    return;
  }
};
//게시글등록 //로그인 필요 //userId->게시글을 할당
router.post('/create', authMiddleware, async (req, res) => {
  const { title, password, content } = req.body;
  const { user } = res.locals; // 미들웨어에서 쿠키에서 추출한 user 정보를 가져옴

  try {
    // 게시글 생성
    const post = await Post.create({
      user: user._id, // 유저 정보를 post schema에 추가
      title,
      password,
      content,
    });

    res.status(201).json({ post });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      errorMessage: '게시글 작성 중 오류가 발생했습니다.',
    });
  }
});

무사히 게시글이 생성되는 것을 확인!!>_<

 

✅알게된 점 

점점 문제 해결 능력이 향상되고, 문제를 해결하는데에 소요되는 시간이 짧아지고있다. 

우선 강의를 들으면서 실시간으로 api 명세서를 총 정리하고 기록해둔 것이 엄청나게 도움이 됐다. 

아직은 전체적인 틀을 구상하는 능력이 부족하기 때문에 a부터 z까지 큰 틀, 그리고 세부사항을 내가 보기 쉽게 정리해두는 습관을 들이면 추후 실습을 하는데에 아주 효율적이라는 것을 다시 한번 느꼈다. 

 

또한 학습을 하며 코드를 그대로 받아적고 외우는 것이 아닌 코드의 연계성과 특히 '인과관계'와 '순서'에 대한 개념을 

정립하는 것이 매~~우 중요하단 생각이 든다. 오늘은 나도 누군가에게 내가 겪고 있는 문제를 자세히 설명해봤는데, 신기하게도 문제를 구체적으로 설명하는 것 만으로도 문제의 원인과 해결방안을 파악하는데 굉장한 도움이 됐다. 

반응형