[Express] Session이란?

2023. 8. 19. 21:14공부 중/Node.js

1. 세션

 

앞서 쿠키를 사용해서 인증을 구현하면 생기는 문제점에 대하여 알아보았다.

 

 

[Node.js] 쿠키의 한계

1. 인증 부분의 한계 민감한 정보를 클라이언트 쪽에 저장한다는 것은 위험하다. 또한 쿠키는 쉽게 탈취당할 수 있기 때문에 민감한 정보를 저장하는 것에 어울리지 않는다. 그렇기 때문에 요즘

ramen4598.tistory.com

 

쿠키를 통해서 인증 기능을 구현하면 민감한 개인 정보를 쿠키에 저장해야 하는 심각한 보안 문제가 발생한다.

 

그래서 이번 기회를 빌어 인증 기능을 구현하기 위햇 많이 사용하는 ‘세션’에 대하여 공부해 보겠다.

 


가. 세션이란?

 

세션(session)은 컴퓨터 과학에서, 특히 네트워크 분야에서 반영구적이고 상호작용적인 정보 교환을 전제하는 둘 이상의 통신 장치나 컴퓨터와 사용자 간의 대화나 송수신 연결상태를 의미하는 보안적인 다이얼로그(dialogue) 및 시간대를 가리킨다. 따라서 세션은 연결상태를 유지하는 것보다 연결상태의 안정성을 더 중요시하게 된다.

 

세션 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. -->

ko.wikipedia.org

 

웹에서는 클라이언트-서버 간의 인증기능을 구현하기 위해서 세션을 활용한다.

 

세션은 사용자의 민감한 정보를 서버에 안전하게 저장하고, 클라이언트의 웹브라우저에는 올바른 접근인지 식별하기 위해서 필요한 최고한의 정보만 저장한다.

 

쿠키만 사용해서 인증 기능을 구현하는 것보다 훨씬 안전하다.

 

 

오픈튜토리얼의 세션 ID를 확인할 수 있다.

 


2. express에서 session 사용

 

가. express-session 설치

 

 

[Express] 미들웨어 사용

0. 참고자료 Express 미들웨어의 사용 - 생활코딩 수업소개 여기서는 미들웨어의 개념을 소개하고, 타인이 만든 미들웨어를 사용하는 방법을 알아봅니다. 강의 1 body-parser를 이용해서 post 방식으로

ramen4598.tistory.com

 

일전에 express팀이 공식적으로 유지보수하는 express middleware에 대하여 살펴보았다.

 

그곳에 보면 “session”을 찾을 수 있다.

 

 

express-session

Simple session middleware for Express. Latest version: 1.17.3, last published: a year ago. Start using express-session in your project by running `npm i express-session`. There are 4707 other projects in the npm registry using express-session.

www.npmjs.com

npm install express-session

 


나. example

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)

세션마다 접속한 횟수를 기억해서 페이지에 표시해 준다.

 

 

connection.sid 쿠키는 사용자를 식별하는 쿠키값이다.

 

서버에 접속할 때마다 웹 브라우저가 서버 쪽에 전송한다.

 

서버는 쿠키에 담긴 세션 아이디로 사용자를 식별한다.

 

사용자에 맞는 데이터를 활용해서 요청을 처리한다.

 


다. 세션 생성, 옵션

// 세션 생성
var session = require('express-session')
...
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

 

우선 세션을 생성하는 미들웨어를 추가한다.

 

session객체를 생성할 때는 몇 가지 옵션을 설정해야 한다.

 

다음 1~3 옵션은 필수 거나 필수에 준하는 옵션들이다.

 

1) secret

필수! 옵션이다.

 

세션 ID 쿠키를 sign 하는 데 사용된다.

 

string이나 string 배열이고, 배열인 경우 sign은 첫 번째 요소만 사용하고, 요청의 signature를 verifying 할 때는 모든 요소를 사용한다.

 

secret은 사람이 알아먹기 어렵게 무질서, 무의미한 문자의 나열로 만드는 것이 안전하다.

 

그리고 외부에 그 값이 유출되면 안 된다.

 

Git 등 버전 관리 프로그램을 사용할 때 소스 코드에 포함하지 않고, 공유되지 않는 별도의 파일을 만들어 보관하자.

 

또한 주기적으로 값을 업데이트하면 좋다. (이때 이전에 사용하던 값은 array에 저장해야 이전에 sign 한 세션 ID도 verifying이 가능하다.)

 

2) resave

  • false : 일반적으로 추천하는 값. 세션 데이터의 변경사항이 생기면 다시 저장.
  • true : 기본값이지만 추천하지 않음. 추후에 기본값이 아니게 될 수 있음. 세션 데이터의 변경사항이 없어도 다시 저장. (session store에 따라서 필요한 경우가 있음. session store에 관해서는 아래에서 설명.)

true는 클라이언트가 paraellel requests를 만들면 race conditions가 발생하기도 한다.

 

사용하는 session store가 어떤 값을 필요로 하는지 모르겠다면 다음과 같이 선택하면 된다.

  1. 사용하는 session store가 touch 메서드를 구현한다면 → false
  2. 그렇지 않음 + store가 저장된 세션들의 expiration date를 설정한다면 → true

 

3) saveUninitialized

  • false : 추천. “uninitialized” 즉 초기화되지 않은 세션을 store에 저장하지 않는다.
  • true : 기본값이지만 추천하지 않음. 추후에 기본값이 아니게 될 수 있음. “uninitialized” 즉 초기화되지 않은 세션을 store에 저장한다.

 

false는 login sessions를 구현하기에 적합하다. storage 사용을 줄일 수 있다.

 

또 쿠키 설정 하기 전에 허가가 필요한 경우 유용하다.

 

여기서 “uninitialized”란 새로 만들어졌지만 수정되지 않은 상태를 뜻한다.

 

4) 그 외 Options

 

너무 많아서 다 정리할 수 없었다.

 

펼쳐보기

더보기

cookie

Settings object for the session ID cookie. The default value is { path: '/', httpOnly: true, secure: false, maxAge: null }.

The following are options that can be set in this object.

cookie.domain

Specifies the value for the Domain Set-Cookie attribute. By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.

cookie.expires

Specifies the Date object to be the value for the Expires Set-Cookie attribute. By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application.

Note If both expires and maxAge are set in the options, then the last one defined in the object is what is used.

Note The expires option should not be set directly; instead only use the maxAge option.

cookie.httpOnly

Specifies the boolean value for the HttpOnly Set-Cookie attribute. When truthy, the HttpOnly attribute is set, otherwise it is not. By default, the HttpOnly attribute is set.

Note be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie.

cookie.maxAge

Specifies the number (in milliseconds) to use when calculating the Expires Set-Cookie attribute. This is done by taking the current server time and adding maxAge milliseconds to the value to calculate an Expires datetime. By default, no maximum age is set.

Note If both expires and maxAge are set in the options, then the last one defined in the object is what is used.

cookie.path

Specifies the value for the Path Set-Cookie. By default, this is set to '/', which is the root path of the domain.

cookie.sameSite

Specifies the boolean or string to be the value for the SameSite Set-Cookie attribute. By default, this is false.

  • true will set the SameSite attribute to Strict for strict same site enforcement.
  • false will not set the SameSite attribute.
  • 'lax' will set the SameSite attribute to Lax for lax same site enforcement.
  • 'none' will set the SameSite attribute to None for an explicit cross-site cookie.
  • 'strict' will set the SameSite attribute to Strict for strict same site enforcement.

More information about the different enforcement levels can be found in the specification.

Note This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.

Note There is a draft spec that requires that the Secure attribute be set to true when the SameSite attribute has been set to 'none'. Some web browsers or other clients may be adopting this specification.

cookie.secure

Specifies the boolean value for the Secure Set-Cookie attribute. When truthy, the Secure attribute is set, otherwise it is not. By default, the Secure attribute is not set.

Note be careful when setting this to true, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.

Please note that secure: true is a recommended option. However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies. If secure is set, and you access your site over HTTP, the cookie will not be set. If you have your node.js behind a proxy and are using secure: true, you need to set "trust proxy" in express:

var app = express()
app.set('trust proxy', 1) // trust first proxy
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
}))

For using secure cookies in production, but allowing for testing in development, the following is an example of enabling this setup based on NODE_ENV in express:

var app = express()
var sess = {
  secret: 'keyboard cat',
  cookie: {}
}

if (app.get('env') === 'production') {
  app.set('trust proxy', 1) // trust first proxy
  sess.cookie.secure = true // serve secure cookies
}

app.use(session(sess))

The cookie.secure option can also be set to the special value 'auto' to have this setting automatically match the determined security of the connection. Be careful when using this setting if the site is available both as HTTP and HTTPS, as once the cookie is set on HTTPS, it will no longer be visible over HTTP. This is useful when the Express "trust proxy" setting is properly setup to simplify development vs production configuration.

genid

Function to call to generate a new session ID. Provide a function that returns a string that will be used as a session ID. The function is given req as the first argument if you want to use some value attached to req when generating the ID.

The default value is a function which uses the uid-safe library to generate IDs.

NOTE be careful to generate unique IDs so your sessions do not conflict.

app.use(session({
  genid: function(req) {
    return genuuid() // use UUIDs for session IDs
  },
  secret: 'keyboard cat'
}))

name

The name of the session ID cookie to set in the response (and read from in the request).

The default value is 'connect.sid'.

Note if you have multiple apps running on the same hostname (this is just the name, i.e. localhost or 127.0.0.1; different schemes and ports do not name a different hostname), then you need to separate the session cookies from each other. The simplest method is to simply set different names per app.

proxy

Trust the reverse proxy when setting secure cookies (via the "X-Forwarded-Proto" header).

The default value is undefined.

  • true The "X-Forwarded-Proto" header will be used.
  • false All headers are ignored and the connection is considered secure only if there is a direct TLS/SSL connection.
  • undefined Uses the "trust proxy" setting from express

rolling

Force the session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown.

The default value is false.

With this enabled, the session identifier cookie will expire in maxAge since the last response was sent instead of in maxAge since the session was last modified by the server.

This is typically used in conjuction with short, non-session-length maxAge values to provide a quick timeout of the session data with reduced potential of it occurring during on going server interactions.

Note When this option is set to true but the saveUninitialized option is set to false, the cookie will not be set on a response with an uninitialized session. This option only modifies the behavior when an existing session was loaded for the request.

store

The session store instance, defaults to a new MemoryStore instance.

unset

Control the result of unsetting req.session (through delete, setting to null, etc.).

The default value is 'keep'.

  • 'destroy' The session will be destroyed (deleted) when the response ends.
  • 'keep' The session in the store will be kept, but modifications made during the request are ignored and not saved.

 


라. 세션 활용

// 세션에 정보를 추가
req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
...

// 세션의 정보에 접근
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')
})

 

req.session는 세션 데이터를 저장하거나 접근하기 위해서 사용된다. (이는 store에 의해 JSON으로 serialized 된다.)

 

지금의 req.session.views는 node.js 서버가 종료되면 지워진다.

 

그 이유는 별다른 옵션을 지정하지 않으면 세션은 기본적으로 서버의 메모리에 저장된다. (MemoryStore)

 

그리고 메모리는 서버가 종료되면 세션이 삭제되기 때문이다.

 

1) Session

  • req.session.regenerate(function(err){…}) : 세션 재생성.

 

  • req.session.destory(function(err){…}) : 세션 제거.

 

  • req.session.reload(function(err){…}) : 저장된 세션 데이터를 다시 불러오는 데 사용.
    1. 변경된 세션 정보를 최신화하기 위해 : 여러 서버나 인스턴스에서 동일한 세션을 사용하고 있는 경우, 한 인스턴스에서 세션 정보가 변경되면 다른 인스턴스의 세션 데이터는 더 이상 최신이 아닐 수 있습니다. 이런 상황에서 session.reload()를 사용하면 저장소에 저장된 최신의 세션 데이터를 다시 불러올 수 있습니다.
    2. 설정된 저장소의 지연이나 비동기 동작 때문에 : 몇몇 세션 저장소는 비동기로 동작하기 때문에 바로 세션 정보를 저장하거나 불러오지 않을 수 있습니다. 만약 세션 정보에 변경이 발생했으나 아직 저장소에 반영되지 않았다면, session.reload()를 사용해 명시적으로 최신 정보를 불러올 수 있습니다.

 

  • req.session.save(function(err){…}) : 세션을 store에 저장.
    해당 메서드는 session 데이터의 변경이 발생하면 HTTP response의 마지막에 자동적으로 호출된다. 그래서 일반적으로 사용할 일이 없다. 하지만 리다이렉션, long-lived requests, webSocket을 사용할 경우 호출해야 할 수 있다. 즉, 응답을 보내기 전에 세션 변경사항이 저장소에 반영되도록 할 수 있습니다.

 

  • req.session.touch() : session.maxAge를 업데이트합니다.
    req.session.cookie.maxAge를 오리지널 값으로 리셋한다. 사용자가 웹사이트를 계속 이용하면 그 사용자의 세션 쿠키는 만료되지 않도록 할 수 있다. (일반적으로 이것은 세션 미들웨어에서 이 작업을 수행하므로 자주 사용하진 않음. Session Options의 rolling 참고.)

 

  • req.session.cookie : 세션 쿠키에 접근.
    • req.session.cookie.expires : 쿠키의 유효 기한.
    • req.session.cookie.maxAge : 쿠키의 남은 시간. 단위는 밀리세컨드. 시간이 지나면 감소한다.
    • req.session.cookie.originalMaxAge : 처음에 설정한 maxAge의 오리지널 값. 단위는 밀리세컨드.

 

  • req.session.id : 세션 ID에 접근. 실제 세션 데이터가 저장될 때 이 ID를 확인할 수 있습니다. Read-only.
  • req.sessionID : 세션 ID에 접근. 요청이 들어올 때마다 바로 확인할 수 있는 세션 ID입니다. Read-only.

 

express-session 미들웨어를 사용할 때 req.session.idreq.sessionID 둘 다 세션의 ID 값을 나타내지만, 사용 목적과 성격이 조금 다르다.

 

  1. req.sessionID:
    • 이것은 현재 요청에 대한 세션 ID를 나타낸다.
    • req.sessionIDexpress-session 초기화 과정에서 생성되며, 실제 세션 객체가 저장소에 저장되기 전에도 접근할 수 있다.
    • 즉, 실제로 세션 데이터가 아직 생성되지 않아도 req.sessionID는 값을 가질 수 있다.
  2. req.session.id:
    • 이것은 저장된 세션 객체의 ID를 나타낸다.
    • req.session 객체는 실제 세션 데이터와 연관된 객체다. 따라서 이 객체의 .id 속성을 통해 세션 ID에 접근하는 것이다.
    • 실제 세션 객체가 생성되기 전에는 req.session 자체가 존재하지 않을 수 있기 때문에, 이 시점에서 req.session.id에 접근하려고 하면 오류가 발생할 수 있다.

 

요약하면…

 

  1. req.sessionID:
    • 요청이 들어올 때마다 바로 확인할 수 있는 세션 ID.
    • 아직 세션 데이터가 만들어지지 않았더라도 이 ID를 볼 수 있음.
  2. req.session.id:
    • 실제 세션 데이터가 저장될 때 이 ID를 확인할 수 있다.
    • 세션 데이터가 아직 준비되지 않았다면 이 ID를 사용할 수 없음.

 

대부분의 경우, 세션 ID를 빠르게 확인하려면 req.sessionID를 사용하면 된다.

 

출처 : https://www.npmjs.com/package/express-session?activeTab=readme

출처 : ChatGPT4.0

 


마. Session store

 

세션에 저장되는 곳을 session store라고 한다.

 

기본적으로 별도의 설정이 없다면 세션은 메모리(MemoryStore)에 저장된다.

 

Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.

 

MemoryStore는 실제 서비스에 사용되기엔 무리가 있다.

 

그래서 express-session은 다양한 store를 지원한다.

 

공식 홈페이지의 “Compatible Session Stores”목차를 살펴보면 지원되는 store의 종류를 살펴볼 수 있다.

 

 

express-mysql-session

A MySQL session store for express.js. Latest version: 3.0.0, last published: 5 months ago. Start using express-mysql-session in your project by running `npm i express-mysql-session`. There are 43 other projects in the npm registry using express-mysql-sessi

www.npmjs.com

 

 

connect-redis

Redis session store for Connect. Latest version: 7.1.0, last published: 3 months ago. Start using connect-redis in your project by running `npm i connect-redis`. There are 928 other projects in the npm registry using connect-redis.

www.npmjs.com

 

1) store methods

 

만약에 session store 저마다 method가 다르면 사용하기 힘들 것이다.

 

그래서 session store의 인터페이스는 통일할 필요가 있다.

 

express-session의 세션 스토어 인터페이스는 구체적인 데이터베이스나 백엔드 저장소에 상관없이 일관된 메서드 세트를 요구한다.

 

이것은 세션 스토어를 자유롭게 교체, 선택할 수 있게 한다.

 

store의 method는 required, recommended, optional로 나뉜다.

 

다음은 주어진 store의 메서드들을 표로 정리한 것입니다: