※ 이 글은 예전 블로그에서 퍼왔으며 2020.2.25에 작성된 글입니다.
※ 주의! 잘못된 정보가 있을수도 있습니다.
프론트엔드를 희망하고 있지만 혼자 서비스를 구현해 보려면 백엔드 분야도 알아야 하기 때문에 한 번 공부해 봤는데 장난이 아닌것 같다..
이해하면 다루는 건 그리 어렵지 않은데 정확한 지식을 얻기 힘들다는 점에서 혼자 공부하는 입장으로선 죽을 맛이다..ㅠㅠ
일단 내가 Node.js와 MySQL 을 이용해 회원가입을 구현하면서 알게된 점과 모르겠는 점, 느낀점을 정리해 볼 것이다.
1. Express 미들웨어를 사용하는 이유
사실 백엔드에 대한 아무런 지식이 없었기 때문에 내가 백엔드를 이해하기 위해선 app.use( ), app.get( ) 같은 미들웨어를 왜 사용하는지 부터 알아야 할 필요가 있었다.
· 라우트(Route)
라우트란 길, 경로란 의미를 지니고 있는 단어이다. 서울에서 부산으로 가기 위한 길이 여러 갈래가 있듯이, 네트워크 상에서도 데이터 패킷이 목적지까지 가기 위한 여러 라우트들이 존재한다.
· 라우터(Router)
라우터는 데이터 패킷이 출발지에서 목적지 까지 이동하기 위한 라우트를 찾고, 데이터 패킷을 어떤 라우트(경로)로 전송할 것인지 정하는 역할을 한다.
· 라우팅(Routing)
라우팅은 라우터가 발견한 라우트 중 최적의 경로(가장 빠른 경로)를 찾아내는 역할을 한다.
(추가적으로, 라우터는 목적지 까지 가는 최단 경로를 찾아내기도 한다. RIP, IGRP, EIGRP, BGP, OSPF, IS-IS 등 여러 가지가 있으며 이것들을 '라우팅 프로토콜'이라 부른다.)
· 마운트(Mount)
마운트는 물리적 장치(하드웨어 장치)를 사용하기 위해 특정한 경로(디렉토리 위치)에 연결시켜 주는 작업.
그래서 app.use( ), app.get( ) 같은 미들웨어를 왜 사용하는 가?
→ callback을 path에 마운트 해주기 위해서 사용한다.
→ use 메서드는 모든 HTTP 메서드에 대해 요청 주소만 일치하면 실행되지만,
get, post, put, patch, delete와 같은 메서드 들은 주소 뿐만 아니라 HTTP 메서드 까지 일치하는 요청일때만 실행된다.
2. Nodejs에서 router분리를 해주는 이유
서비스의 규모가 점점 커지다 보면 라우터의 규모도 점점 커지고 한 파일내의 코드의 길이도 점점 길어진다.
그래서 router(혹은 routes)라는 이름의 파일을 만들어 라우터 처리를 분리하여 관리해준다.
(보통은 app.use(router); 선언을 하여 사용하는데 이 문장은 일반 라우터들 보다는 뒤에 위치해야 다른 라우터 들을 방해하지 않는다.)
3. body-parser 더 이상 사용하지 말자
일단 parser에 대해서 얘기하자면..
· 파서(parser)
데이터를 내가 원하는 형태의 데이터로 가공하는 과정을 파싱(parsing)이라고 하는데 이 과정을 수행하는 모듈 혹은 메서드.
body-parser를 사용할 필요가 없어진 이유는 Express v4.16.0을 기준으로 express도 빌트인 body-parser를 넣었기 때문이다. 그래서 기존의 사용하던 문장을..
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json()); // json으로 받아들인 정보를 분석함
app.use(express.urlencoded({ extended: true }));
// 이 옵션이 false면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석하고,
// true면 qs 모듈을 사용하여 쿼리스트링을 해석한다
이렇게 body-parser 모듈을 이용하지 않고 express로 대체하는 것으로 사용가능하다.
4. 모듈선언은 ES6 문법인 import를 사용해서 할 수도 있다.
아직 ES6에 대해 자세히 알지 못해서 몰랐던 정보였다;;
const express = require("express"); // 대신,
import express from "express"; // 을 사용하자.
· 참고하면 좋은 자료
5. 쿠키(Cookie)와 세션(Session)에 대해서
· 쿠키(Cookie)
브라우저에 저장되는 키와 값이 들어있는 작은 데이터 파일.
(웹서버의 정보를 웹브라우저에 저장해서 개인화, 인증, 사용자 추적 등의 기능을 구현할 수 있도록 해줌.)
(쿠키는 서버에 저장되는 것이 아니라 브라우저에 저장되는 정보임.)
(쿠키는 유출, 왜곡될 수 있기 때문에 개인정보를 저장하는데는 적합하지 않음.)
· 세션(Session)
일정 시간동안 방문자가 웹 브라우저를 통해 웹 서버에 접속한 시점으로부터 웹 브라우저를 종료함으로써 연결을 끝내는 시점.
(웹브라우저에 저장하기 곤란한 정보를 웹서버에서 세션파일을 만들어 서버 자체에 저장함.)
(세션을 남발하게 되면 서버가 과부화의 걸릴 위험이 있음.)
· 참고하면 좋은 자료들
6. connection.query("sql문", ( err, rows, fields ) => { ... })에 대해
나는 err, rows, fields 파라미터(parameter)들이 대체 무엇이며 어떤 프로퍼티(property)를 가질 수 있는지 너무 궁금했다. 그래서 구글링의 결과 다음과 같은 의미를 지닌 다는 것을 알게 되었다.
· err(error)
sql문을 실행시키고 에러가 났을시 에러를 출력한다.
에러가 없을시에는 NULL값을 가짐.
· rows
rows는 RowDataPacket 객체를 지니며 key: value형태의 값을 가진다.
key값은 MySQL에서 Field에 해당하는 것들이다.
(rows[index].property로 원하는 값을 꺼낼수도 있다.)
console.dir(rows);를 했을시 다음과 같이 나옴.
[
RowDataPacket {
UID: 1,
user_name: 'so_tired',
user_email: 'www8565@naver.com',
user_id: 'www8565',
user_pw: '1234'
}
]
· fields
fields는 MySQL에서 Field에 해당하는 것들에 대한 세부적인 정보를 가진다.
console.dir(fields);를 했을시 다음과 같이 나옴.
[
FieldPacket {
catalog: 'def',
db: 'account',
table: 'user',
orgTable: 'user',
name: 'UID',
orgName: 'UID',
charsetNr: 63,
length: 11,
type: 3,
flags: 16899,
decimals: 0,
default: undefined,
zeroFill: false,
protocol41: true
},
...
]
7. 회원가입을 구현하기 위해 사용한 모듈들
· passport
사용자 인증을 위한 모듈.
· passport-local
로그인 기능을 집적 구현할 때 사용하는 모듈.
· helmet
HTTP 헤더를 설정하여 웹 취약성 문제로 부터 보호하기 위한 모듈.
· express-session
Express 프레임 워크에서 세션을 관리하기 위해 필요한 모듈.
· express-mysql-session
세션 정보를 MySQL에 저장하기 위한 패키지 모듈.
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const session = require("express-session");
const helmet = require("helmet");
const MySQLStore = require("express-mysql-session")(session);
// 세션처리
router.use(session({
// secret: 세션을 설정할 때의 key값, 혹시 모를 세션 해킹을 대비하기 위해 주기적으로 값 변경
secret: "dngmd;dlrj;wlfdjajrdmfzheld",
// resave: 세션을 저장하고 불러오는 과정에서 세션을 다시 저장할건지 선택가능
resave: true,
// saveUninitialized: 세션을 저장할 때 초기화를 할 것인지
saveUninitialized: true,
// cookie: 쿠키 설정 (maxAge: 최대 머무르는 시간, httpOnly: 해커가 세션 접근을할 때 자신이 만든 프로그램을 이용해 접근하는 것을 막아줌)
cookie: { maxAge: 600000, httpOnly: true },
// store: 세션 보안 관련
store: new MySQLStore({
host: 'localhost',
user: 'root',
port: 3306,
password: 'mysql_pw',
database: 'db_name'
}),
//rolling: 로그인 상태에서 다른페이지로 이동할 때마다, 세션 값에 변화를 줄건지 등등..
rolling: true
}));
// passport 초기화
router.use(passport-initialize());
//passport session
router.use(passport.session());
// helmet 처리
router.use(helmet.hsts({
maxAge: 10886400000,
includeSubDomains: true
}));
/*
Done 함수는 next() 함수와 비슷하다고 보면된다.
첫번째 인자로 에러를 두번째 인자로 유저 데이터를 넘겨주면 넘어가는 인자에 따라 그 다음 실행될 함수가 결정된다.
*/
// serialize: 어떤 정보를 쿠키에 전달할지 설정하는 것.
passport.serializeUser((user, done) => {
done(null, user.user_id);
});
// deserialize: serialize를 통해 받아온 쿠키의 데이터를 어떻게 사용자로 전환할지 설정하는 것.
passport.deserializeUser((user_id, done) => {
console.log('passport session get id:', user_id);
done(null, user_id);
});
8. 회원가입을 구현하기 위해 겪었던 문제들..
끝내 원하는 수준의 회원가입 기능을 만들어 내진 못했다.
나는 이름, 이메일, 아이디, 패스워드 4가지 항목을 받아 회원가입하는 기능을 만들고 싶었다.
그래서 MySQL에 column을 생성할 때 이름과 아이디는 중복허용을 금지하기 위해 UNIQUE선언을 해주었다.
여기서부터 문제가 발생했는데..
문제는 입력값과 데이터 값을 비교하였을 때, Duplicate 관련 에러가 뜬다는 것이다.
Error: ER_DUP_ENTRY: Duplicate entry 'so_tired' for key 'user.user_name'
물론 UNIQUE 선언을 해제하면 문제가 해결되겠지만;; 그렇게 하긴 싫어서 버그를 고치려고 시도해봤다..
크게 2가지 방법을 생각해 봤다.
1. Duplicate 에러가 발생했을 시 서버가 중지되지 않고 동작하게 하는 법.
2. passport 모듈을 이용하여서 입력값을 받아 중복값일 경우 회원가입 실패 창을 띄우는 것.
첫번째 방법은 구글링 해봤지만 저 오류가 발생했을 때 UNIQUE선언을 해제하라는 내용 뿐이여서 포기했다.
두번째 방법은 나름 희망이 있었다.
하지만 멘탈이 터져버린 부분은 2개 이상의 값을 받아 중복인지 검사하도록 하려면 어떻게 해야하는지 몰랐다.
(물론 구글링도 하고 passport 사이트 까지 들어가 봤는데 모르겠는 걸..ㅠㅠ)
(구글링을 해도 usernameField와 passwordField 두가지 값만 받아서 사용하는 예제밖에 없으니 어떻게 해야할지 도저히 모르겠드라;;)
의문점은 다음과 같다.
1. 아래 문장에서 "local"이 의미하는 건 무엇인지. 그리고 Field는 무조건 usernameField와 passwordField 밖에 못생성하는지.
passport.use('local', new LocalStrategy({
usernameField: "입력값",
passwordField: "입력값",
passReqToCallback: true
}), ... );
이것만 어떻게 하면 희망이 보일거 같은데... 진짜 모르겠다;;
+ 200226 추가
"local"의 의미는 그냥 이름이였다..
즉 "local"이라는 이름의 전략(Strategy)를 사용할 것라고 선언한 것이다.