Node.js开发入门—使用cookie保持登录

原创 2015年08月17日 07:04:38

这次来做一个网站登录的小例子,后面会用到。这个示例会用到Cookie、HTML表单、POST数据体(body)解析。

第一个版本,我们的用户数据就写死在js文件里。第二个版本会引入MongoDB来保存用户数据。

示例准备

1. 使用express创建应用

就下面的命令序列:

express LoginDemo
cd LoginDemo
npm install

2. 登录页面

登录页面的jade模板为login.jade,内容如下:

doctype html
html
  head
    meta(charset='UTF-8')
    title 登录
    link(rel='stylesheet', href='/stylesheets/login.css')
  body
    .form-container
      p.form-header 登录
      form(action='login', method='POST', align='center')
        table
          tr
            td
              label(for='user') 账号:
            td
              input#user(type='text', name='login_username')
          tr
            td
              label(for='pwd') 密码:
            td
              input#pwd(type='password', name='login_password')
          tr
            td(colspan='2', align='right')
              input(type='submit', value='登录')
    p #{msg}

login.jade放在views目录下。我在login.jade里硬编码了汉字,注意文件用UTF-8编码。

这个模板的最后是一条动态消息,用于显示登录错误信息,msg变量由应用程序传入。

我给login页面写了个简单的CSS,login.css文件,内容如下:

form {
  margin: 12px;
}
a {
  color: #00B7FF;
}

div.form-container {
  display: inline-block;
  border: 6px solid steelblue;
  width: 280px;
  border-radius: 10px;
  margin: 12px;
}

p.form-header {
  margin: 0px;
  font: 24px bold;
  color: white;
  background: steelblue;
  text-align: center;
}

input[type=submit]{
  font: 18px bold;
  width: 120px;
  margin-left: 12px;
}

请把login.css放在public/stylesheets目录下。

3. profile页面

登录成功后会显示配置页面,profile.jade页面内容:

doctype html
html
  head
    meta(charset='UTF-8')
    title= title
  body
    p #{msg}
    p #{lastTime}
    p 
      a(href='/logout') 退出

profile.jade放在views目录下。profile页面显示一条登录成功的消息,还显示上次登录时间,最后提供了一个退出链接。

4. app.js改动

我改动了app.js,以便用户在没有登录时访问网站自动跳转到login页面。新的app.js内容如下:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.all('*', users.requireAuthentication);
app.use('/', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});


module.exports = app;

5. users.js

我修改了users.js,把认证、登录、登出等逻辑放在里面,首先要把users.js转为UTF-8编码(sorry,硬编码了汉字哈)。内容:

var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function hashPW(userName, pwd){
  var hash = crypto.createHash('md5');
  hash.update(userName + pwd);
  return hash.digest('hex');
}

// just for tutorial, it's bad really
var userdb = [
    {
      userName: "admin",
      hash: hashPW("admin", "123456"),
      last: ""
    },
    {
      userName: "foruok",
      hash: hashPW("foruok", "888888"),
      last: ""
    }
  ];

function getLastLoginTime(userName){
  for(var i = 0; i < userdb.length; ++i){
    var user = userdb[i];
    if(userName === user.userName){
      return user.last;
    }
  }
  return "";
}

function updateLastLoginTime(userName){
  for(var i = 0; i < userdb.length; ++i){
    var user = userdb[i];
    if(userName === user.userName){
      user.last = Date().toString();
      return;
    }
  }
}

function authenticate(userName, hash){

  for(var i = 0; i < userdb.length; ++i){
    var user = userdb[i];
    if(userName === user.userName){
      if(hash === user.hash){
          return 0;
      }else{
          return 1;
      }
    }
  }

  return 2;
}

function isLogined(req){
  if(req.cookies["account"] != null){
    var account = req.cookies["account"];
    var user = account.account;
    var hash = account.hash;
    if(authenticate(user, hash)==0){
      console.log(req.cookies.account.account + " had logined.");
      return true;
    }
  }
  return false;
};

router.requireAuthentication = function(req, res, next){
  if(req.path == "/login"){
    next();
    return;
  }

  if(req.cookies["account"] != null){
    var account = req.cookies["account"];
    var user = account.account;
    var hash = account.hash;
    if(authenticate(user, hash)==0){
      console.log(req.cookies.account.account + " had logined.");
      next();
      return;
    }
  }
  console.log("not login, redirect to /login");
  res.redirect('/login?'+Date.now());
};

router.post('/login', function(req, res, next){
  var userName = req.body.login_username;
  var hash = hashPW(userName, req.body.login_password);
  console.log("login_username - " + userName + " password - " + req.body.login_password + " hash - " + hash);
  switch(authenticate(userName, hash)){
  case 0: //success
    var lastTime = getLastLoginTime(userName);
    updateLastLoginTime(userName);
    console.log("login ok, last - " + lastTime);
    res.cookie("account", {account: userName, hash: hash, last: lastTime}, {maxAge: 60000});
    res.redirect('/profile?'+Date.now());
    console.log("after redirect");
    break;
  case 1: //password error
    console.log("password error");
    res.render('login', {msg:"密码错误"});
    break;
  case 2: //user not found
    console.log("user not found");
    res.render('login', {msg:"用户名不存在"});
    break;
  }
});

router.get('/login', function(req, res, next){
  console.log("cookies:");
  console.log(req.cookies);
  if(isLogined(req)){
    res.redirect('/profile?'+Date.now());
  }else{
    res.render('login');
  }
});

router.get('/logout', function(req, res, next){
  res.clearCookie("account");
  res.redirect('/login?'+Date.now());
});

router.get('/profile', function(req, res, next){
  res.render('profile',{
    msg:"您登录为:"+req.cookies["account"].account, 
    title:"登录成功",
    lastTime:"上次登录:"+req.cookies["account"].last
  });
});

module.exports = router;

如你所见,我内置了两个账号,admin和foruok,登录时就验证这两个账号,不对就报错。

好了,执行“npm start”,然后在浏览器里打开“http://localhost:3000”,可以看到下面的效果:

view-logindemo.png

折腾几次,登录,退出,再次登录,效果如下:

view-logindemo-profile.png

好啦,这就是这个示例的效果。接下来我们来解释一下用到概念和部分代码。

处理POST正文数据

我们在示例中使用了HTML表单来接收用户名和密码,当input元素的类型为submit时,点击它,浏览器会把表单内的数据按一定的格式组织之后编码进body,POST到指定的服务器地址。用户名和密码,在服务器端,可以通过HTML元素的名字属性的值找出来。

服务器解析表单数据这一过程,我们不用担心,用了express的body-parser中间件,它会帮我们做这件事,只要做简单的配置即可。而且这些配置代码,express generator都帮我们完成了,如下:

//加载body-parser模块
var bodyParser = require('body-parser');
...
//应用中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

我们处理/login路径上的POST请求的代码在users.js里,从“router.post(‘/login’…”开始(94行,要是markdown能自动给代码插入行号就好了)。引用登录表单内的用户名的代码如下:

var userName = req.body.login_username;

注意到了吧,express.Request对象req内有解析好的body,我们使用login_username来访问用户名。而login_username就是我们在HTML里的input元素的name属性的值。就这么关联的。password也类似。

cookie

cookie,按我的理解,就是服务器发给浏览器的一张门票,要访问服务器内容,可以凭票入场,享受某种服务。服务器可以在门票上记录一些信息,从技术角度讲,想记啥记啥。当浏览器访问服务器时,HTTP头部把cookie信息带到服务器,服务器解析出来,校验当时记录在cookie里的信息。

HTTP协议本身是无状态的,而应用服务器往往想保存一些状态,cookie应运而生,由服务器颁发,通过HTTP头部传给浏览器,浏览器保存到本地。后续访问服务器时再通过HTTP头部传递给服务器。这样的交互,服务器就可以在cookie里记录一些用户相关的信息,比如是否登录了,账号了等等,然后就可以根据这些信息做一些动作,比如我们示例中的持久登录的实现,就利用了cookie。还有一些电子商务网站,实现购物车时也可能用到cookie。

cookie存储的是一些key-value对。在express里,Request和Response都有cookie相关的方法。Request实例req的cookies属性,保存了解析出的cookie,如果浏览器没发送cookie,那这个cookies对象就是一个空对象。

express有个插件,cookie-parser,可以帮助我们解析cookie。express生成的app.js已经自动为我们配置好了。相关代码:

var cookieParser = require('cookie-parser');
...
app.use(cookieParser());

express的Response对象有一个cookie方法,可以回写给浏览器一个cookie。

下面的代码发送了一个名字叫做“account”的cookie,这个cookie的值是一个对象,对象内有三个属性。

res.cookie("account", {account: userName, hash: hash, last: lastTime}, {maxAge: 60000});

res.cookie()方法原型如下:

res.cookie(name, value [, options])

文档在这里:http://expressjs.com/4x/api.html#res.cookie

浏览器会解析HTTP头部里的cookie,根据过期时间决定保存策略。当再次访问服务器时,浏览器会把cookie带给服务器。服务器使用cookieParser解析后保存在Request对象的cookies属性里,req.cookies本身是一个对象,解析出来的cookie,会被关联到req.cookies的以cookie名字命名的属性上。比如示例给cookie起的名字叫account,服务端解析出的cookie,就可以通过req.cookies.account来访问。注意req.cookies.account本身既可能是简单的值也可能是一个对象。在示例中通过res.cookie()发送的名为account的cookie,它的值是一个对象,在这种情况下,服务器这边从HTTP请求中解析出的cookie也会被组装成一个对象,所以我们通过req.cookies.account.account就可以拿到浏览器通过cookie发过来的用户名。但如果浏览器没有发送名为“account”的cookie,那req.cookies.account.hash这种访问就会抛异常,所以我在代码里使用req.cookies[“account”]这种方式来检测是否有account这个cookie。

持久登录

如果用户每次访问一个需要鉴权的页面都要输入用户名和密码来登录,那就太麻烦了。所以,很多现代的网站都实现了持久登录。我的示例使用cookie简单实现了持久登录。

在处理/login路径上的POST请求时,如果登录成功,就把用户名、一个hash值、还有上次登录时间保存在cookie里,并且设置cookie的有效期为60秒。这样在60秒有效期内,浏览器后续的访问就会带cookie,服务端代码从cookie里验证用户名和hash值,让用户保持登录状态。当过了60秒,浏览器就不再发送cookie,服务端就认为需要重新登录,将用户重定向到login页面。

现在服务端的用户信息就简单的放在js代码里了,非常丑陋,下次我们引入MongoDB,把用户信息放在数据库里。


其它文章:

版权声明:本文为foruok原创文章,转载请通过订阅号“程序视界”联系foruok获取授权。

Nodejs express操作cookie和session

express使用cookiparser来解析cookie 直接使用req.cooikes.name 就可以取得客户端发来的cookie cookie: var express = require(...
  • pretent
  • pretent
  • 2015年04月22日 23:23
  • 9943

nodejs开发中关于cookie及session的设置

一、安装cookie相关的模块 1、npm install cookie-parser 二、在app.js文件中配置cookie相关的内容 1、引入模块文件//引入cookie文件 const coo...
  • kuangshp128
  • kuangshp128
  • 2017年07月15日 13:01
  • 662

Node.JS学习——使用cookie 和 session

众所周知,HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据,如何能把一个用户的状态数据关联起来呢? cookie 首先产生了 cook...
  • HWJSuper
  • HWJSuper
  • 2015年05月27日 11:05
  • 2614

nodejs死亡笔记之cookie和session(宇宙级框架express)

首先,我必须义正言辞的吐槽一下这个宇宙级框架!express3.x和expss4.x差别怎么就那么大呢?找了好多资料来学习,但总是莫名其妙的报错,一开始我以为是因为我长得不好看,后来发现。。。我用的是...
  • qq_31655965
  • qq_31655965
  • 2016年08月17日 11:04
  • 6921

Node.js实战关于cookie-parser中间件

最近在kindle上看Node.js实战这本书的电子版,书的内容虽然已经旧了但是概念和条理还是比较清晰,但是说实话电子版是不是错太多了点儿..照着例子打一遍基本上都要改改才能对(不得不说也是一种锻炼-...
  • sooooooooad
  • sooooooooad
  • 2016年08月08日 18:03
  • 3888

nodejs创建cookie方法

方法一、 res.cookie("username","xiaoming"); 方法二、 res.setHeader("Set-Cookie",a); res.setHeader("Set-Cook...
  • u011674177
  • u011674177
  • 2013年12月11日 18:28
  • 1045

NodeJS获得Cookie

校园卡APP,获得验证码图片的通知获得Cookie的内容var http = require( "http" ); var url = require( "url" );var urlstring =...
  • u010181895
  • u010181895
  • 2016年01月05日 00:02
  • 742

Node爬坑记——伪造cookie

写在前面 在没有引入NodeJS层之前,客户端和服务端之前的数据传输可以用Ajax来完成,而且服务端可以直接读取客户端请求头携带的cookie,这个直接走 HTTP 协议,没有任何问题。但是当引入了...
  • u011413061
  • u011413061
  • 2016年01月21日 23:31
  • 1744

nodejs关于session和cookie的问题

原文链接:https://github.com/alsotang/node-lessons/tree/master/lesson16众所周知,HTTP 是一个无状态协议,所以客户端每次发出请求时,下一...
  • u012679583
  • u012679583
  • 2016年01月13日 12:05
  • 2584

express中cookie的使用和cookie-parser的解读

最近在研究express,学着使用cookie,开始不会用,就百度了一下,没有百度到特别完整的解答。查阅了express的API,综合了网友的博客,解读了cookie-parser的源码,以及使用We...
  • xiaohubeiplus
  • xiaohubeiplus
  • 2016年04月20日 09:56
  • 3174
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Node.js开发入门—使用cookie保持登录
举报原因:
原因补充:

(最多只允许输入30个字)