Miracle Morning, LHWN

1. Node.js express 프레임워크의 session 본문

IT 기술/[React] Project

1. Node.js express 프레임워크의 session

Lee Hye Won 2021. 5. 31. 17:05

# 최근 웹 어플리케이션은 쿠키를 통해 직접 인증을 구현하지 않고, 쿠키는 사용자를 식별하는 용도로만 사용한다.

대신 실제 데이터는 session을 통해 서버 쪽에 안전하게 파일이나 데이터베이스의 형태로 저장한다.

# 여기서는 인증 라이브러리인 express를 사용해본다. (※ plugin과 middleware는 비슷한 개념)

(※ express 는 프론트인 React 를 이용해서 백엔드 서버를 만들 수 있도록 해주는 프레임워크이다.)

 

npm install -s express session
// 여기서 -s는 "이 프로젝트는 express session 모듈에 의존하고 있다"는 의미

 

# 예제 소스 (express session github에서 공식적으로 제공하고 있는 샘플 소스)

 

// nodejs/express-session.js

var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session')

var app = express()

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

app.use(function (req, res, next) {
  if (!req.session.views) {
    req.session.views = {}
  }

  // get the url pathname
  var pathname = parseurl(req).pathname

  // count the views
  req.session.views[pathname] = (req.session.views[pathname] || 0) + 1

  next()
})

app.get('/foo', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/foo'] + ' times')
})

app.get('/bar', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/bar'] + ' times')
})

app.listen(3000, function () {
    console.log('Port 3000');
}); 

 

# nodemon도 추가로 설치해보았다.

※ nodemon : 서버 코드를 변경할 때마다 서버를 재시작하는게 귀찮을 때 nodemon을 사용하면 이를 자동으로 재시작해준다.

※ nodemon : 서버 코드를 변경할 때마다 서버를 재시작하는게 귀찮을 때 nodemon을 사용하면 이를 자동으로 재시작해준다.

 

# 위 소스를 실행하면 사용자를 식별할 수 있는 connect.sid 쿠키 값이 생성된다.

서버에 접속할 때마다 (새로고침) 웹 브라우저가 서버로 connect.sid 쿠키 값을 전송해주고,

서버에서는 쿠키 값을 가지고 그 쿠키에 해당하는 사용자의 데이터를 조작한다 = 1씩 증가해준다.

 

 

session middleware의 동작

 

서버 <> 클라이언트간 HTTP 통신을 할 때, 서버가 쿠키를 발행하고 클라이언트가 쿠키를 저장한다.

그리고 나중에 재접속을 할 때 서버는 클라이언트의 쿠키 값을 확인한다. (비교 주체 : 쿠키)

만일 세션을 이용한다면 쿠키에 세션의 sid 값만을 저장해둔다. 따라서 서버에서는 sid 값을 가지고 서버 내부에 저장되어 있는

데이터베이스 혹은 파일과 비교하여 내용을 확인하는 메커니즘인 것이다. (비교 주체 : 세션의 sid) → 보안 강화

 

session을 이용한 기본 동작 구현

 

→ 결과 : 리로드 될 때마다 세션의 num 값이 증가된다.

 

var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session')

var app = express()

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

app.get('/signIn', function (req, res, next) {
console.log(req.session); // 위 소스가 req 안에 session 객체를 추가한다.ㅏ

    if (req.session.num === undefined) {
        req.session.num = 10; // 처음에는 num 안에 값이 없기 때문에 10이 지정됨
    } else {
        req.session.num += 1; // 이후 요청부터는 1씩 더해짐
    }
    res.send(`Views : ${req.session.num}`);

})

app.listen(3000, function () {
console.log('Port 3000');
});

 

# app.use() : 사용자 요청이 있을 때마다 해당 코드가 실행되도록 약속.

# secret : id 값. 식별자

→ 나만 가지고 있어야 하는 비밀옵션!

→ 나중에 프로젝트에서는 별도의 파일에 저장해놓고 변수처리해놔야 한다.

<!-- resave, saveUninitialized는 특별한 이유가 있지 않은 이상 아래처럼 세팅 -->

# resave: false

→ 세션 데이터에 변화가 있을 때에만! 저장한다.

(true면 계속 저장한다.)

# saveUninitialized: true

→ 세션이 필요하기 전까지는 세션을 구동시키지 않는다.

(false면 세션이 필요하든 필요없든 세션을 구동시킨다.)

 

app.use(session({ // app이 session을 사용. session 함수 실행 → 미들웨어가 내부적으로 개입
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

 

근데 위 예제에서 num은 휘발성 데이터이기 때문에 서버를 재구동하면 데이터가 초기화된다.

이를 해결하기 위해 File 형태의 store에 저장해보자!

 

session 객체의 옵션 알아보기

 

var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session') // 미들웨어를 모듈로서 설치해준다.
var FileStore = require('session-file-store')(session);

var app = express()

app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
store: new FileStore()
}))

app.get('/signIn', function (req, res, next) {
console.log(req.session);

    if (req.session.num === undefined) {
        req.session.num = 10;
    } else {
        req.session.num += 1;
    }
    res.send(`Views : ${req.session.num}`);

})

app.listen(3000, function () {
console.log('Port 3000');
});

호환되는 store을 선택해서 구현을 할 수 있는데 여기에서는 File 형태에 저장할 것이다.

(외 호환되는 방식은 링크 참고 : http://expressjs.com/en/resources/middleware/session.html)

 

Express session middleware

express-session Installation This is a Node.js module available through the npm registry. Installation is done using the npm install command: $ npm install express-session API var session = require('express-session') session(options) Create a session middl

expressjs.com

 

var FileStore = require('session-file-store')(session);

위 소스를 실행하면 req.session 내용이 파일로 생성되어 저장되며, 생성된 파일을 보면 요청할 때마다 num 값이 바뀌는 것을 볼 수 있다.

(실제 프로젝트에서는 session 내용을 데이터베이스나, 해싱 데이터베이스에 저장해두어야 한다.)

→ 사용자의 계정 (닉네임 등)은 세션에 담아두면 데이터베이스나 파일에 다시 접속할 필요가 없어서 효율적이다.

사용자가 sessionId를 가지고 있는 상태에서 서버로 접속하면, 쿠키에 sessionId를 담아 서버로 전달한다.

→ 미들웨어가 sessionId를 가지고 세션 store에서 해당 Id 값에 대응하는 파일을 읽는다.

→ 파일 데이터를 기반으로해서 req 객체의 session 프로퍼티에 객체를 추가해준다.

→ 사용자가 num 값을 바꾸면 (+1) req.send를 통해 파일의 값을 또 업데이트해준다.

 

로그인 구현

 

전체 소스는 강사님의 git 에 있으니 참고!

https://github.com/web-n/express-session-auth/tree/f4630ffd99e5edd6d4baaef1b5a36a9f83b982fc

 

web-n/express-session-auth

Contribute to web-n/express-session-auth development by creating an account on GitHub.

github.com

 

# 로그인했을 때! HTTP 응답(response) header로 connect_sid라는 세션의 식별자를 쿠키에 저장한다.

→ 해당 connect_sid 에 해당하는 파일은 서버쪽에 저장한다.

# 로그아웃할 때는 request.session.destroy 함수를 이용한다.

→ 로그인할 때 생성되었던 파일이 삭제되면서 다시 로그아웃에 대한 새로운 파일이 생성된다. (로그아웃에 대한 새로운 세션을 발급한 것이다.)

 

세션 저장 작업

 

# 아래와 같이 로그인 유효성 검사를 하는 로직이 있다고 하자.

// 문제가 좀 있는 소스
if(email === authData.email && password === authData.password {
  request.session.is_logined = true;
  request.session.nickname = authData.nickname;
  response.redirect('/');
} else {
  response.send('who?');
}

 

위 과정이 모두 끝난 다음에 세션 미들웨어는 우리가 기록한 데이터(is_logined, nickname)를 세션 store에 기록하는 작업을 한다.

(메모리에 저장된 세션 데이터를 저장소에 반영하는 작업이다.)

근데 저장소에 담겨있는 데이터의 양이 많아져서 저장하는 작업 속도가 현저히 느려진다면,

값이 바뀌기도 전에 response.redirect('/'); 가 실행될 수도 있다. (저장소에 값을 저장하지도 않았는데 redirect가 발생하는 것이기 때문에

사용자 입장에서는 '나는 로그인을 했는데 로그인이 안된 상태의 '/' 페이지가 보여지네', '아까 로그인 안되더니 한참뒤에 보니까 또 로그인이 되어있네')

# 이런 현상을 방지하기 위해 사용하는 것이 request.session.save() 함수이다.

이 save() 함수를 이용하면 세션 객체(request.session)에 있는 데이터 (is_logined, nickname)를 세션 store에 반영하는 작업을 바로 시작한다.

그리고 이 작업이 모두 끝난 뒤에야 인자로 전달된 콜백함수를 실행하게 된다. 이 콜백함수에는 당연히 redirect 함수를 담아야 할 것이다.

즉, store에 반영작업이 모두 끝난 뒤에 redirect가 일어날 수 있도록 보장하는 작업이다.

 

// 문제를 해결한 소스
if(email === authData.email && password === authData.password {
  request.session.is_logined = true;
  request.session.nickname = authData.nickname;

  reqeust.session.save(function() {
    response.redirect('/');
  }) 
} else {
  response.send('who?');
}

 

로그인 인증에 대한 보안

# secure 옵션

HTTP로 웹 서버 <> 브라우저간 통신을 하게되면 누군가가 connect_sid를 가로채서 대신 로그인 할 수도 있다.

그래서 HTTPS로 통신을 해주어야 하는데 secure 옵션에 true 값을 주면 HTTPS에서만 세션 정보를 주고받을 수 있도록 처리할 수 있다.

 

app: use(session({
  secure: true,
  secret: 'xxx',
  resave: false,
  saveUninitialized: true,
  store: new FileStore()
}))

 

# HttpOnly 옵션

사용자가 전송하는 데이터에 스크립트가 포함되어 있고, 그 스크립트가 활성화될 수 있는 환경이라면 사용자는 스크립트를 이용해서

세션 쿠키 아이디를 탈취해서 공격자에게 전송할 수 있다. 이럴 경우에는 사용자가 전송하는 데이터에서 스크립트를 사용할 수 없도록 설정해주어야 할 것이다.

세션의 옵션으로 HttpOnly: true 으로 지정하여 자바스크립트를 통해서 세션 쿠키를 사용할 수 없도록 강제할 수 있다.

 

app: use(session({
  HttpOnly: true,
  secret: 'xxx',
  resave: false,
  saveUninitialized: true,
  store: new FileStore()
}))

 

타사인증 (federation authentication) 도구들

일반적인 서비스는 사용자의 정보를 데이터베이스에 저장하고 이를 활용하는 방향으로 운영되기에 '회원가입'과 '로그인' 과정이 필수적으로 요구된다. 그러나 최근에는 facebook, twitter, github, google, kakao 등 타사의 유저 정보를 이용한 회원가입 및 로그인 서비스 즉, 타사인증 (Federation Authentication) 을 도입해 인증체계는 대기업에서 수행하도록 하여 안전하고 간편하게 구현한다.

타사인증을 해주는 도구는 oauth가 있는데 이를 또 대신해주는 도구로 Node.js의 대표적인 인증 라이브러리(통합 인증 관리 패키지)인 passport-js가 있다.

Comments