쿠키와 세션, 인증

웹 브라우저의 쿠키에 대해서 배우고, 쿠키를 기반으로 사용자를 인증하는 데 쓰이는 세션을 공부합니다. 로그인 기능이 있는 할일 리스트 서비스를 만듭니다.
쿠키, 세션


쿠키

session-id라는 쿠키의 생성 과정session-id라는 쿠키의 생성 과정

쿠키(Cookie)는 웹 브라우저에 저장되어있는 키-값 형식의 데이터입니다. 쿠키는 팝업창을 몇 일간 띄우지 않기, 회원 로그인 등과 같은 기능을 구현 할 때 사용 할 수 있습니다. 크롬의 경우 개발자 도구 Application 탭에서 현재 저장되어있는 쿠키들을 확인 할 수 있습니다.

  1. 쿠키는 웹 서버가 Response을 보낼때 Set-Cookie 헤더를 통해서 생성됩니다.
  2. Set-Cookie 헤더를 받은 웹 브라우저는 저장장치에 데이터를 저장합니다.
  3. 이후 웹 브라우저가 Request를 보낼 때, 웹 서버의 도메인에 해당되는 모든 쿠키 값이 Cookie 헤더를 통해 함께 전달됩니다. 이를 통해 서버는 Request를 보낸 클라이언트의 고유한 상태를 구분 할 수 있습니다.

쿠키(Cookie)란 하이퍼 텍스트의 기록서(HTTP)의 일종으로서 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 그 사이트가 사용하고 있는 서버에서 인터넷 사용자의 컴퓨터에 설치하는 작은 기록 정보 파일을 일컫는다. '쿠키’라는 이름은 그림 동화 '헨젤과 그레텔’에서 가져온 것이다. 헨젤과 그레텔이 지나온 길을 표시하기 위해 쿠키 조각을 떨어뜨리며 표시했다는 이야기에서 따온 것이다. HTTP 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. 이 기록 파일에 담긴 정보는 인터넷 사용자가 같은 웹사이트를 방문할 때마다 읽히고 수시로 새로운 정보로 바뀐다… 누군가의 쿠키를 훔쳐서 해당 사용자의 웹 계정 접근권한을 획득 할 수도 있다… (위키백과)

Set-Cookie 헤더와 Cookie 헤더

// 쿠키 생성 요청
app.get('/set',(req,res)=>{
    res.setHeader('Set-Cookie','key1=val1');
    res.setHeader('Set-Cookie',['key2=val2', 'key3=val3']);
    res.cookie('key4','val4');
    
    /**
    Response에 아래와 같은 Set-Cookie 헤더들이 추가됩니다.
    Set-Cookie: key1=val1
    Set-Cookie: key2=val2
    Set-Cookie: key3=val4
    Set-Cookie: key4=val5
    **/

    res.send("웹 브라우저님 이 쿠키들 좀 만들어 주세요.");
});

// 클라이언트의 쿠키 이용하기
app.get('/get', (req,res)=>{
    res.send("네 웹 브라우저가 보낸 쿠키들: "+req.headers.cookie);
});

// cookie-parser 미들웨어
const cookieParser = require('cookie-parser');
app.use(cookieParser());

app.get('/get2', (req,res)=>{
    res.send("네 웹 브라우저가 보낸 쿠키 key1: "+req.cookies.key1);
});

JavaScript로 쿠키 제어

HTTP Response의 Set-Cookie 헤더를 주는 방법 외에, 웹 브라우저에서 직접 JavaScript로 쿠키를 제어 할 수도 있습니다.

세션과 인증

쿠키를 이용한 인증

웹 서버는 수 많은 요청들이 들어올 때 어떤 요청이 어떤 사용자로부터 오는지 구분해야 할 필요가 있습니다. 하지만 HTTP 프로토콜의 연결에는 지속성이 없기 때문에 웹 서버에서 클라이언트를 구분하고, 클라이언트의 상태를 관리하기 위해서는 쿠키를 이용해야 합니다.

요청이 올 때마다 쿠키를 통해서 클라이언트를 구분하고 그 상태를 확인 할 수 있습니다. Cookie: name=dongwook을 가진 Request와 Cookie: name=gaeddong을 가진 Request에 따라 다른 Respone를 줄 수 있습니다. 이를 위해서 form 태그를 통해 유저의 아이디와 패스워드를 받고, 올바른 경우에 Set-Cookie: name=dongwook과 같이 쿠키를 생성 응답을주면, 이후엔 name 쿠키를 확인해서 클라이언트를 구분 할 수 있겠습니다.

하지만, 쿠키는 쉽게 조작이 가능하기 때문에 인증 방식을 위처럼 구현해서는 위험합니다.

세션 쿠키를 이용한 인증

쿠키와 세션쿠키와 세션

세션(Session)이란 개념이 도입됩니다.

  • 요청을 보내는 웹 브라우저(클라이언트)를 고유하게 구분 할 수 있는 특수한 값을 쿠키로 갖게 할 것입니다. 이 쿠키를 세션 쿠키라고 합니다. 이 세션 쿠키의 키를 임의로 session_id로 합니다.
  • 클라이언트의 Request을 받으면 웹 서버는 session_id 쿠키가 없는 경우 Set-Cookie: session_id=aksdjabskd21424aslxnadsasdi192ru와 같이 고유하고, 예측이 힘든 값을 주며 쿠키를 생성하도록 응답합니다. 동시에 메모리 등에 aksdjabskd21424aslxnadsasdi192ru와 일대일로 대응되는 데이터를 생성합니다. 이렇게 서버에 보관되는 데이터를 세션이라고 합니다.
  • 이후 클라이언트가 Cookie: session_id=aksdjabskd21424aslxnadsasdi192ru를 통해 본인을 밝히며 Request를 보내는 경우, 웹 서버에서는 해당 세션 쿠키와 대응되는 세션을 통해 클라이언트의 고유한 상태(인증 정보 등)를 이용하거나 갱신 할 수 있습니다. 세션을 이용하면 민감한 데이터를 클라이언트에게 노출시키지 않고 클라이언트의 상태를 제어 할 수 있습니다.

Express, express-session 미들웨어

const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({
  secret: 'benzen',
  resave: true,
  saveUninitialized: true
}));

app.use((req,res,next) => {
    /* 
        express-session 미들웨어를 사용하면 req 객체에 .session 속성이 생깁니다.
        이 값은 요청을 보내는 클라이언트 별로 고유하며, 여기에 클라이언트의 고유한 데이터를 관리 할 수 있습니다.
    */
    req.session.count = (typeof req.session.count == 'undefined') ? 1 : req.session.count+1;
    console.log(`${req.sessionID}: request ${req.session.count}`);
    next();
})

// 세션에 따라 다른 Response를 주기
app.get('/', (req,res)=>{
    if (req.session.admin) {
        res.send(`
<html>
  <body>
    <h1>Hello Admin!</h1>
    <a href="/logout">Logout!</a>
  </body>
</html>
`);
    } else {
        res.send(`
<html>
  <body>
    <form action="/login" method="get">
      <input type="text" name="id" placeholder="User ID">
      <input type="text" name="pw" placeholder="Password">
      <input type="submit" value="Login!">
    </form>
  </body>
</html>
`);
    }
});

// 로그인
app.get('/login',(req,res)=>{
    if (req.query.id=='admin' && req.query.pw=='1234')
        req.session.admin = true;

    res.redirect('/');
});

// 로그아웃
app.get('/logout',(req,res)=>{
    delete req.session.admin;

    res.redirect('/');
});

app.listen(8000);

Express의 express-session 미들웨어 API

TodoList 서비스

todo1.pngtodo1.png

todo2.pngtodo2.png

로그인 기능이 있는 TodoList 서비스를 만들어보겠습니다.

TodoList 서비스 스펙

  • 서버의 메모리에 데이터를 유지하기
  • id, password를 통한 유저 로그인 기능
  • 유저당 하나의 리스트에 할 일을 추가하고 삭제 할 수 있음
  • 404 Not Found를 처리하는 미들웨어 만들기
  • 에러 미들웨어 만들기

이때 POST로 Request를 받을 때 바디에 실려오는 폼 데이터를 해석하기 위해서는 body-parser 미들웨어를 사용합니다.

Express, body-parser 미들웨어

const bodyParser = require('body-parser');

// application/x-www-form-urlencoded을 파싱해주는 미들웨어
app.post('/write', bodyParser.urlencoded(), (req,res)=>{
    /**
    req.body에 폼 데이터가 파싱되어 있습니다.
    req.body.inputName1
    req.body.inputName2
    **/

    // ...
});

Express의 body-parser 미들웨어 API

todolist/data.js

module.exports=[
{
    id: 1,
    uid: 'admin',
    password: '1234',
    list: [
        '워크샵 하기',
        '책 읽기'
    ]
},
{
    id: 2,
    uid: 'kim',
    password: '2222',
    list: [
        '양파사기',
        '달걀 사오기'
    ]
},
{
    id: 3,
    uid: 'son',
    password: '3333',
    list: [
        '화분 물주기',
        '강아지 밥주기'
    ]
}];

todolist/index.js

const express = require('express');
const session = require('express-session');
const app = express();

app.set('views', 'views');
app.set('view engine', 'ejs');

// 세션 미들웨어
app.use(session({
  secret: 'todolist',
  resave: true,
  saveUninitialized: true
}));

// / => /list로 리다이렉션
app.get('/',(req,res)=>{
    res.redirect('/list');  
});

// res.locals를 사용하여 뷰에서 세션 데이터를 바로 사용할 수 있게
app.use((req, res, next) => {
    res.locals.user = req.session.user || null;
    next();
});

// 인증 확인 미들웨어
app.use((req,res,next)=>{
    if(req.session.user || req.url=='/user/login'){
        next();
    } else {    
        res.redirect('/user/login');
    }
});

// 라우터(컨트롤러)를 다른 파일로 분리하기
app.use('/user', require('./controllers/login-router'));
app.use('/list', require('./controllers/list-router'));
app.use(require('./controllers/not-found'));
app.use(require('./controllers/error'));

app.listen(4000);

전체 소스코드

목차
4. 웹 백엔드
5. 데이터베이스
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 10일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.