Juni_Dev_log

(node.js) [Part.5] 웹 서버 만들기 - 쿠키와 세션 관리하기 본문

Theorem (정리)/node.js

(node.js) [Part.5] 웹 서버 만들기 - 쿠키와 세션 관리하기

Juni_K 2020. 12. 11. 17:29

사용자가 로그인한 상태인지 아닌지 확인하고 싶을 때는 쿠키세션을 사용한다.

쿠키는 클라이언트 웹 브라우저에 저장되는 정보이며, 세션은 웹 서버에 저장되는 정보이다. 여기에서는 이 쿠키와 세션을 어떻게 다룰 수 있는지 알아볼 것이다.

쿠키 처리하기

쿠키는 클라리언트 웹 브라우저에 저장되는 정보로서, 일정 기간 동안 저장하고 싶을 때 사용한다.

익스프레스에서는 cookie-parser 미들웨어를 사용하면 쿠키를 설정하고 확인할 수 있다. 다음과 같이 use() 메소드를 사용해  cookie-parser 미들웨어를 사용하도록 만들면 요청 객체에 cookies 속성이 추가된다.

 

앞에서 만들어 둔 app10.js 자바스크립트 파일을 복사하여 새로운 app11.js 를 만들고 일부분을 수정한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
...
 
// 쿠키를 다루는 모듈 사용
var cookieParser = require('cookie-parser');
 
...
 
// cookieParser 사용하기
app.use(cookieParser);
 
...
 
// 라우터 객체 설정
var router = express.Router();
 
router.route('/process/showCookie').get(function(req,res){
    console.log('/process/showCookie 호출됨.');
    
    res.send(req.cookies);
});
 
router.route('/process/setUserCookie').get(function(req,res){
    console.log('/process/setUserCookie 호출됨.');
    
    // 쿠키 설정
    res.cookie('user',{
        id : 'mike',
        name : '소녀시대',
        authorized : true
    });
    
    // redirect 로 응답
    res.redirect('/process/showCookie');
});
 
// 라우터 객체를 app 객체에 등록
app.use('/',router);
 
 
...
cs

 

cookie-parser 미들웨어를 사용하도록 설정한 후, 라우팅 미들웨어의  get() 메소드를 호출하여 /process/showCookie 와  /process/setUserCookie 패스를 처리하는 콜백 함수를 등록한다.

 

클라이언트의 쿠키 정보는 cookies 객체에 들어 있는데 사용자가 응답 문서를 조회할 때, 쿠키 정보를 볼 수 있도록 /process/showCookie 패스를 처리하는 함수에서 이 쿠키 객체를 응답으로 보낸다. 

그러면, 클라이언트에서는 쿠키 객체를 그대로 전달받게 된다.

 

/process/setUserCookie 패스를 처리하는 함수에서는 응답 객체의 cookie() 메소드롤 user 쿠키를 추가한다.

그러면 쿠키가 클라이언트 웹 브라우저에 설정되는데 마지막 코드 부분에서 redirect() 메소드로 /process/showCookie 패스를 다시 요청한다.

그러므로 그 정보는 그대로 다시 클라이언트의 웹 브라우저에 표시된다. 다음은 쿠키가 클라이언트에 저장된 후 사용자에게 표시될 때까지의 과정이다.

 

쿠키를 설정하고 사용자에게 보여주는 과정

 

일반적으로는 쿠키를 설정하고 나면 그 정보는 다른과정에 사용되지만 여기에서는 설정된 쿠키 정보를 단순히 확인만 했다. 쿠키를 사용할 수 있도록 만들어주는 cookie-parser 모듈은 외장모듈이므로 명령 프롬프트에서 다음 명령으로 설치한다.

 

npm install cookie-parser --save

 

그 다음 app11.js를 실행한 후 웹 브라우저를 열고, /process/setUserCookie 패스로 요청한다.

 

서버에서 user 라는 이름으로 설정한 쿠키 정보를 웹 브라우저 화면에 보여준다.

쿠키가 브라우저에 제대로 설정했는지 확인하기 위해서 크롬 브라우저의 개발자 도구 화면을 띄운다. 개발자 도구에서 [Application] 탭을 클릭하면 브라우저에 저장된 리소스를 보여준다. [Cookies] 항목을 클릭하면 현재 PC의 IP 정보가 보이는데, 그 IP 정보를 클릭하면 오른쪽에 설정된 쿠키 정보가 표시된다.

쿠키를 저장하고 나면 이곳에서 'user' 라는 이름으로 추가된 쿠키를 볼 수 있다.

세션 처리하기

이번에는 세션을 만들어보자.

세션도 상태정보를 저장하는 역할을 하지만, 쿠키와 달리 서버 쪽에 저장된다. 

세션을 사용하는 대표적인 예로는 로그인을 했을 때 저장하는 세션을 들 수 있다.

 

사용자가 로그인을 하면 세션이 만들어지고 로그아웃을 하면 세션이 삭제되는 기능을 만들면 사용자가 로그인하기 전에는 접근이 제한된 페이지를 보이지 못하도록 설정할 수 있다.

즉, 사용자가 상품 페이지처럼 접근이 제한된 페이지를 조회했을 때 로그인 상태가 아니라면 로그인 페이지를 자동으로 열어줘야한다. 그러면 로그인을 해야 상품 페이지로 이동할 수 있으며 상품 페이지에서 로그아웃을 할 수 있다.

 

세션을 사용한 로그인 처리 과정

 

세션이 있을 때와 없을 때 처리하는 방식이 달라지므로 전체 처리 과정은 약간 복잡해 보일 수 있다.

하나씩 차근차근 살펴보면 먼저 사용자가 상품정보를 웹 서버에 요청한다.

상품 정보를 요청할 때는 GET 방식으로 /process/product 패스로 요청한다. 웹 서버에서는 /process/product 패스를 라우팅하여 처리하는데, 그 안에서 user 라는 이름으로 된 세션이 있는지 확인한다.

만약, 이미 저장된  user 세션이 있다면 클라이언트에 /public/product.html 파일을 응답으로 전송한다.

그러면 사용자는 상품정보를 볼 수 있다.

만약, user 세션이 없다면 로그인을 유도하기 위해 로그인 페이지로 이동한다.

redirect()  메소드로 login2.html 로그인 페이지를 보여준다.

 

로그인 페이지에서 처리하는 방식은 앞에서 만든 로그인 기능과 큰 차이가 없다.

하지만 /process/login 패스를 라우팅하여 처리할 때 로그인에 성공했으면 user 세션을 저장하고 클라이언트에게는 로그인 성공 페이지를 보여 준다는 점에서 다르다.

로그인 성공 페이지는 코드로 만들어서 응답할 수도 있고 별도의 파일을 만든 후, 그 파일을 전송할 수도 있다.

여기에서는 코드로 전송하는 방식을 사용할 것이다.

로그인 성공 페이지에는 상품 정보를 조회하는 링크를 넣어 해당 링크를 클릭했을 때 /process/product 를 요청한다.

로그아웃을 처리하는 함수는 /process/logout 패스로 처리한다.

 

이런 처리 과정을 맞게 동작하려면 세션을 저장하는 방법에 대해서 배워야한다.

익스프레스에서는 세션을 지원하기 위해서, express-session  모듈을 사용한다. 명령프롬프트에서 모듈을 설치한다.

 

npm install express-session --save

 

코드에서는 require() 모듈을 통해서 불러들인다. 세션을 사용할 때도 쿠키도 같이 사용하므로 cookie-parser도 같이 부른다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser');
var static = require('serve-static');
var errorHandler = require('errorhandler');
var cookieParser = require('cookie-parser');
var expressSession = require('express-session');
 
...
 
app.use(cookieParser);
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
 
...
 
cs

express-session 모듈은 미들웨어로 사용되기 때문에 use() 메소드를 사용해서 미들웨어에 추가한다.

use() 메소드에는 세션 객체를 호출하여 반환되는 객체를 전달한다. 초기화에 전달되는 값에는 secret 속성에 키 값을 넣어준다.

이제 라우팅 함수를 만들 수 있다. 사용자가 처음에 상품 정보를 보기 위해 요청할 /process/product 패스에 콜백 함수를 다음과 같이 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
var router = express.Router();
...
// 상품 정보 라우팅 함수
router.route('/process/product').get(function(req,res){
    console.log('/process/product 호출됨.');
    
    if(req.session.user){
        res.redirect('/public/product.html');
    }else{
        res.redirect('/public/login2.html');
    }
});
 
// 라우터 객체를 app 객체에 등록
app.use('/',router);
...
cs

 

이 함수에서는 user 세션이 있는지 확인한 후, 만약 세션 객체가 있으면 /public/product.html 파일을 응답으로 보내어 상품 정보를 볼 수 있게 한다.

하지만,  user 세션이 없다면 로그인 페이지를 보여준다. 로그인 페이지는 앞에서 만든 /public/login2.html 파일을 그대로 사용한다.

[public] 폴더에 들어있는 product.html 파일은 상품 정보를 보여주기 위한 웹 문서이다. 다음과 같이 간단하게 태그를 넣는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>상품 페이지</title>
</head>
<body>
    <h3>상품정보 페이지</h3>
    <hr/>
    <p>로그인 후 볼 수 있는 상품정보 페이지입니다.</p>
    <br><br>
    <a href="/process/logout">로그아웃하기</a>
</body>
</html>
cs

문서의 가장 아래쪽에는 로그아웃을 할 수 있도록 <a>태그를 추가하고 클릭했을 때, /process/logout 을 호출한다.

로그인 페이지에서는 로그인을 시도하는 경우에는 /process/login 패스로 라우팅되도록 다음과 같이 로그인에 필요한 함수를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
// 로그인 라우팅 함수 - 로그인 후 세션 저장
router.route('/process/login').post(function(req,res){
    console.log('/process/login 호출됨.');
    
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    if(req.session.user){
        //이미 로그인된 상태
        console.log('이미 로그인되어 상품 페이지로 이동합니다.');
        
        res.redirect('/public/product.html');
        
    }else{
        //세션 저장
        req.session.user = {
            id:paramId,
            name:'소녀시대',
            authorized:true
        };
        res.writeHead('200',{'Content-Type':'text/html;charset=utf8'});
        res.write('<h1>로그인 성공</h1>');
        res.write('<div><p>Param id : ' + paramId +  '</p></div>');
        res.write('<div><p>Param Password : '+ paramPassword +'</p></div>');
        res.write("<br><br><a href='/process/product'>상품 페이지로 이동하기</a>");
        res.end();
    }
});
...
cs

uesr 세션이 있는 경우에는 /public/product.html 페이지를 보여주고 그렇지 않으면 로그인을 시도한 후 user 세션을 저장한다. user 객체를 세션으로 저장하고 싶다면 요청 객체 안에 있는 session 객체의 속성으로 user 객체를 넣어주면된다.

user 객체에는 id,name,authorized 속성을 넣어 보았다. user 세션을 저장한 후에는 클라이언트로 응답을 보낸다.

로그인이 성공했음을 알리기 위해 클라이언트로 보낸 응답 코드를 보면 가장 아래쪽에 상품페이지로 이동할 수 있는 링크가 있다.

이제 마지막으로 로그아웃을 처리할 수 있는 함수를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
// 로그아웃 라우팅 함수 - 로그아웃 후 세션 삭제함
router.route('/process/logout').get(function(req,res){
    console.log('/process/logout 호출됨.');
    
    if(req.session.user){
        //로그인된 상태
        console.log('로그아웃 합니다.');
        
        req.session.destory(function(err){
            if (err) {throw err;}
            
            console.log('세션을 삭제하고 로그아웃되었습니다.');
            res.redirect('/public/login2.html');
        });
    }else{
        //로그인 안된 상태
        console.log('아직 로그인되지 않습니다.');
        
        res.redirect('/public/login2.html');
    }
});
...
cs

로그아웃할 때는 session 객체에 정의된 destory() 메소드를 호출하여 세션을 제거한다.

세션을 없앤 후에는 redirect() 메소드로 /public/login2.html 페이지를 전송한다.

 

이제 서버를 실행한 후 localhost:3000/process/product 주소로 요청한다.

처음에는 user 세션 객체가 만들어져있지 않으므로 다음과 같이 로그인 페이지가 나타난다.

 

(app12.js 최종본)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
 
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
 
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');
 
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
 
// Session 미들웨어 불러오기
var expressSession = require('express-session');
 
 
// 익스프레스 객체 생성
var app = express();
 
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
 
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
 
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
 
app.use('/public'static(path.join(__dirname, 'public')));
 
// cookie-parser 설정
app.use(cookieParser());
 
// 세션 설정
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
 
 
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
 
// 로그인 라우팅 함수 - 로그인 후 세션 저장함
router.route('/process/login').post(function(req, res) {
    console.log('/process/login 호출됨.');
 
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    if (req.session.user) {
        // 이미 로그인된 상태
        console.log('이미 로그인되어 상품 페이지로 이동합니다.');
        
        res.redirect('/public/product.html');
    } else {
        // 세션 저장
        req.session.user = {
            id: paramId,
            name'소녀시대',
            authorized: true
        };
        
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h1>로그인 성공</h1>');
        res.write('<div><p>Param id : ' + paramId + '</p></div>');
        res.write('<div><p>Param password : ' + paramPassword + '</p></div>');
        res.write("<br><br><a href='/process/product'>상품 페이지로 이동하기</a>");
        res.end();
    }
});
 
// 로그아웃 라우팅 함수 - 로그아웃 후 세션 삭제함
router.route('/process/logout').get(function(req, res) {
    console.log('/process/logout 호출됨.');
    
    if (req.session.user) {
        // 로그인된 상태
        console.log('로그아웃합니다.');
        
        req.session.destroy(function(err) {
            if (err) {throw err;}
            
            console.log('세션을 삭제하고 로그아웃되었습니다.');
            res.redirect('/public/login2.html');
        });
    } else {
        // 로그인 안된 상태
        console.log('아직 로그인되어있지 않습니다.');
        
        res.redirect('/public/login2.html');
    }
});
 
// 상품정보 라우팅 함수
router.route('/process/product').get(function(req, res) {
    console.log('/process/product 호출됨.');
    
    if (req.session.user) {
        res.redirect('/public/product.html');
    } else {
        res.redirect('/public/login2.html');
    }
});
 
app.use('/', router);
 
 
// 404 에러 페이지 처리
var errorHandler = expressErrorHandler({
    static: {
      '404''C:/Users/kks13/OneDrive/바탕 화면/Dev/Study/nodejs/nodejs_study/ExpressExample/public/404.html'
    }
});
 
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
 
 
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
 
 
cs

/process/product로 요청했을 때 나타나는 로그인 페이지

아이디와 비밀번호를 입력한 후 [전송]버튼을 누른다.

그러면 서버에서 로그인한 상태를 유지하기 위해서 user세션을 저장한 후 로그인이 성공했다는 웹 문서를 보여준다.

 

로그인을 시도했을 때 보이는 로그인 성공 메시지

화면 아래쪽에 있는 '상품 페이지로 이동하기' 링크를 누르면 다음과 같이 상품 페이지가 표시된다.

 

상품 페이지 표시

상품 페이지 아래쪽에 있는 '로그아웃하기' 링크를 클릭하면 user 세션 객체를 삭제한 후, 다시 로그인 화면으로 이동한다.

 

로그아웃 후 보이는 로그인 화면

로그인 화면에서 로그인하여 세션이 만들어지면 connect.sid 쿠키가 브라우저에 저장된다.

connect.sid 쿠키는 웹 브라우저에서 세션 정보를 저장할 때 만들어진 것이다.

브라우저마다 이름이 다를 수 있지만 쿠키를 사용해 세션 정보를 저장하는 방식은 같다.

 

지금까지 로그인 처럼 일정 상태를 유지할 수 있도록 만들어주는 세션에 대해서 알아보았다.

Comments