nodejs-身份认证-v1.0.0

cookie

在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地(浏览器存),当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。

使用示例
//steps-01:安装依赖
cnpm install cookie-parser

//steps-02:建服务器
//cookie-server.js
var express = require('express');
var cookie = require('cookie-parser');
var router = express.Router();

outer.get('/', function (req, res, next) {

    //设置cookie
    res.cookie('username','zhangsan');
    res.cookie('username','zhangsan',{maxAge:10000}); //有效期以毫秒为单位

    //获取cookie
    console.log(req.cookies);
    console.log(req.cookies.username);

    //删除cookie
    res.clearCookie('username');

    res.render('/index', {title: '首页'});
});
app.listen(3000);

http://wiki.jikexueyuan.com/project/node-lessons/cookie-session.html

session

用户登录成功,服务端会创建和保存一个session(服务器存),并给客户端一个sessionId。客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。

//steps-01:创建项目
express -e

//steps-02:安装依赖
npm install
npm install --save express-session session-file-store

//steps-03:建服务器
//session-server.js
var express = require('express');
var app = express();
var session = require('express-session');
var FileStore = require('session-file-store')(session);

var identityKey = 'skey';

app.use(session({
    name: identityKey,
    secret: 'chyingp',  // 用来对session id相关的cookie进行签名
    store: new FileStore(),  // 本地存储session(文本文件,也可以选择其他store,比如redis的)
    saveUninitialized: false,  // 是否自动保存未初始化的会话,建议false
    resave: false,  // 是否每次都重新保存会话,建议false
    cookie: {
        maxAge: 10 * 1000  // 有效期,单位是毫秒
    }
}));

//steps-04:账户数据
//users.js
module.exports = {
    items: [
        {name: 'chyingp', password: '123456'}
    ]
};

//steps-05:登陆接口
//session-server.js
var users = require('./users').items;

var findUser = function(name, password){
    return users.find(function(item){
        return item.name === name && item.password === password;
    });
};

// 登录接口
app.post('/login', function(req, res, next){
    
    var sess = req.session;
    var user = findUser(req.body.name, req.body.password);

    if(user){
        req.session.regenerate(function(err) {
            if(err){
                return res.json({ret_code: 2, ret_msg: '登录失败'});                
            }
            
            req.session.loginUser = user.name;
            res.json({ret_code: 0, ret_msg: '登录成功'});                           
        });
    }else{
        res.json({ret_code: 1, ret_msg: '账号或密码错误'});
    }   
});

//steps-05:退出接口
//session-server.js
app.get('/logout', function(req, res, next){
    req.session.destroy(function(err) {
        if(err){
            res.json({ret_code: 2, ret_msg: '退出登录失败'});
            return;
        }
        
        // req.session.loginUser = null;
        res.clearCookie(identityKey);
        res.redirect('/');
    });
});

//steps-06:判登陆态
app.get('/', function(req, res, next){
    var sess = req.session;
    var loginUser = sess.loginUser;
    var isLogined = !!loginUser;

    res.render('index', {
        isLogined: isLogined,
        name: loginUser || ''
    });
});

//steps-07:建客户端

//steps-08:启客户端
存在哪里

其中,开发环境存内存就好了。一般的小程序为了省事,如果不涉及状态共享的问题,用内存 session 也没问题。但内存 session 除了省事之外,没有别的好处。

用 cookie session 的话,是不用担心状态共享问题的,因为 session 的 data 不是由服务器来保存,而是保存在用户浏览器端,每次用户访问时,都会主动带上他自己的信息。当然在这里,安全性之类的,只要遵照最佳实践来,也是有保证的。它的弊端是增大了数据量传输,利端是方便。

缓存方式是最常用的方式了,即快,又能共享状态。相比 cookie session 来说,当 session data 比较大的时候,可以节省网络传输。推荐使用。

数据库 session。除非你很熟悉这一块,知道自己要什么,否则还是老老实实用缓存吧。

session 的 store 有四个常用选项:1)内存 2)cookie 3)缓存 4)数据库

cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。

JWT(JSON WEB Token)

一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。

头信息指定了该JWT使用的签名算法

实现示例
//steps-01:创建头部
{
//类型
"typ": "JWT",
//算法
"alg": "HS256"
}
//转成字符:base64

//steps-02:创建荷载
//标准部分(建议但不强制使用)
iss: jwt签发者
sub: jwt发送方
aud: jwt接收方
iat: jwt的签发时间
exp: jwt的过期时间
nbf: jwt的可用起点
jti: jwt的唯一标识
//公共部分

//私有部分
{
"name":"Free码农",
"age":"28",
"org":"今日头条"
}
//转成字符:base64


//steps-03:创建签证
//头部
//荷载
//密码

使用示例
//安装依赖
{"dependencies": {
    "body-parser": "^1.17.2",
    "express": "^4.15.3",
    "express-jwt": "^5.3.0",
    "jsonwebtoken": "^7.4.1",
    "mongoose": "^4.10.4",
    "morgan": "^1.8.2"
 }}

//配置文件
//config.js
module.exports = {
    'network' : {
    'port':8080
  },    
  'jwtsecret': 'myjwttest',
  'database': '数据库链接或者其他'
};

//数据文件
//user.js
var mongoose = require('mongoose');

var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({ 
    name: String, 
    password: String, 
    admin: Boolean
}));

//建服务器
/*
技术注释://
业务注释://-
*/
var express     = require('express');
var bodyParser  = require('body-parser');
var morgan      = require('morgan');
var mongoose    = require('mongoose');
var jwt    = require('jsonwebtoken'); 
var config = require('./config');
var User   = require('./user');

var app         = express();

//连数据库
mongoose.connect(config.database); 
//设全局量
app.set('superSecret', config.jwtsecret);

//使用插件
//析请求体
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//访问日志
app.use(morgan('dev'));

//路径处理
app.get('/', function(req, res) {
res.send('JWT 授权访问的API路径 http://localhost:' + config.network.port + '/api');
   });
 //存数据库
app.post('/setup', function(req, res) {
    if(req.body.name && req.body.password){  
        var nick = new User({ 
        name: req.body.name, 
        password: req.body.password,
        admin:req.body.admin||false
          }); 

        nick.save(function(err) {
         if (err) throw err;
        console.log('用户存储成功');
        res.json({ success: true });
  });}
  else{
      res.json({ success: false,msg:"错误参数" });
  }
});
//获取令牌
app.post('/authenticate', function(req, res) {
    User.findOne({
        name: req.body.name
     }, function(err, user) {
        if (err) throw err;

        if (!user) {
          res.json({ success: false, message: '用户账号错误' });
        } else if (user) {
           if (user.password != req.body.password) {
            res.json({ success: false, message: '用户密码错误' });
          } else {
        var token = jwt.sign(user, app.get('superSecret'), {
              expiresIn : 60*60*24// 授权时效24小时
        });
        res.json({
              success: true,
              message: '请使用您的授权码',
              token: token
        });
      }   
    }
    });
});

//接口监听
app.listen(config.network.port);
console.log('JWT测试服务已经开启地址: http://localhost:' + config.network.port);

适用场景

JWT适合一次性的命令认证,颁发一个有效期极短的JWT,即使暴露了危险也很小,由于每次操作都会生成新的JWT,因此也没必要保存JWT,真正实现无状态。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容