后台开发之cookie、session详解

821 阅读3分钟

后台开发之cookie、session详解

cookie和session,为浏览器和web服务器之间**维持状态(登录态、重要信息等)**提供了有效的途径,是每个后台开发必须要掌握的技能。

先说说为什么需要cookie和session,大家都知道http协议是无状态的,那么怎么在浏览器和服务器之间保持状态呢?

比如只有你登录了某购物网站,你才能看到自己的用户信息、购物车信息、订单信息,不能每次打开购物车你都需要重新登录一下,那用户不是烦死了。所以,当你登录一次之后,服务器端就种下cookie返回给浏览器,浏览器以后每次访问服务器都会带上这个cookie(浏览器行为),这样一来服务器解析这个cookie后就知道是哪个用户了。

下面先介绍一下概念,接着代码实现一下,光说不练假把式。

概念介绍

  • cookie:信息保存在浏览器端。当浏览器第一次访问时,服务器种下cookie返回给浏览器,以后每次访问时浏览器都会带上这个cookie。cookie有大小(单个cookie保存的数据不能超过5 K)和数量的限制(很多浏览器都限制一个站点最多保存20个cookie)。

  • Session:信息保存在服务器端,是基于cookie传输的。当浏览器第一次访问时,服务器创建session的同时将sessionId保存到cookie中返回给浏览器,以后每次访问浏览器都带上含有这个sessionId的cookie,服务器拿到这个sessionId,在内存或redis中解析出信息。

问题分析

为什么有了cookie还需要session?

cookie是将信息存储浏览器端的,很容易被篡改或伪造,而session是将sessionId返给浏览器,仅仅能看到的是一串很长的乱码,除非绝顶高手很难破解。

让我们假设一种场景,用户A登录后,进入首页,显示“欢迎A访问首页”。

此时,我们简单的伪造一下username,然后再访问这个页面。

可以看到很容易就被伪造了信息。

应该怎么解决这个问题呢?

解决方案:

  • 签名,通过签名来增加安全性。利用加密算法hmac(username, secret) -> ‘xxxxxxxxx’,即Cookie: uername=username|xxxxxxxxx,这样即使修改了uername,但签名对不上,可以判定是伪造的信息。

  • cookie中存放sessionId,server端对应username -> 即session

    Cookie:sessionId=s%3Aa4hag_CW42vcykKBdxxxxxx,在服务器端存储sessionId所对应的数据,即使你想篡改sessionId也不知道从何改起,很大程度上保证了安全性。

代码实现

一、签名

  • 自己实现签名过程
const express = require('express');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');

const Hmac = (val, secret) => {
    const hmac = crypto.createHmac('sha256', secret)
                        .update(val)
                        .digest('Base64');
    return hmac;
}

const app = express();

app.use(cookieParser());

let secret = 'abcd';

app.get('/login', (req, res) => {
    const hmac = Hmac(req.query.username, secret);  // 生成签名
    res.cookie(`username`, `${req.query.username}|${hmac}`, {signed: false});
    res.send('登录成功');
});

app.get('/pageOne', (req, res) => {
    console.log('Cookies:', req.cookies);   // 普通cookie
    if (req.cookies.username) {
        let usernameArr = req.cookies.username.split('|');
        console.log('usernameArr=', usernameArr);
        const hmac = Hmac(usernameArr[0], secret);  // 签名校验
        if (hmac !== usernameArr[1]) {
            res.send('您的cookie是伪造的');
            return;
        }
        res.send(`欢迎${req.cookies.username}访问首页`);
    } else {
        res.send('您还未登录,请先登录')
    }
});

app.listen(8002, () => {
    console.log('Server start. port=', 8002);
});
  • 利用cookie-parser中间件,该中间件的实现原理与上面类似,只是做了一层封装
const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();

app.use(cookieParser('abcd'));

app.get('/login', (req, res) => {
    res.cookie(`id`, 1234, {signed: false});
    res.cookie(`username`, `${req.query.username}`, {signed: true});
    res.send('登录成功');
})

app.get('/pageOne', (req, res) => {
    console.log('Cookies:', req.cookies);   // 普通cookie,Cookies: { id: '1234' }
    console.log('Signed Cookies:', req.signedCookies);  // 带签名的cookie,{ username: 'A' }

    if (req.signedCookies.username) {
        res.send(`欢迎${req.signedCookies.username}访问首页`);
    } else {
        res.send('您还未登录,请先登录')
    }
})

app.listen(8002, () => {
    console.log('Server start. port=', 8002);
})

二、session

  • 自己实现,实现过程为在服务器端生成sessionId,同时将对应的信息存储下来,可以放在内存中也可以放在redis中,原理大概就是这样,这里实现略了。
  • 利用express-session中间件
const express = require('express');
const cookieParser = require('cookie-parser');
const session = require('express-session');

// redis 模块
var redis   = require('redis');
var client  = redis.createClient('6379', '127.0.0.1');// 默认监听6379端口,'127.0.0.1'为你本地ip(默认不需要修改)
var redisStore = require('connect-redis')(session);

const app = express();

app.use(cookieParser());

// redis 链接错误
client.on("error", function(error) {
    console.log('redis error:', error);
});

app.use(session({               // 设置cookie,返回浏览器的是connect.sid(默认)=s%3A4o9vyxxxxxx
    name: 'sessionId',
    secret: 'abcd',             // secret的值建议使用随机字符串
    rolling: true,              // true,到期时间会被重置为原始的maxAge
    saveUninitialized: false,   // false,未初始化的session的cookie将不会在response中设置
    cookie: {
        maxAge: 6 * 60 * 1000   //  过期时间(毫秒)
    },
    store: new redisStore()     // (使用redis的存储session)
}));

app.get('/login', (req, res) => {
    res.cookie(`id`, 1234);
    req.session.username = req.query.username;
    console.log('login cookies:', req.cookies);
    console.log('login session:', req.session); 
    res.send('登录成功');
});

app.get('/pageOne', (req, res) => {
    console.log('pageOne cookies:', req.cookies);
    console.log('pageOne session:', req.session);   
    if (req.session.username) {
        res.send(`欢迎${req.session.username}访问首页`);
    } else {
        res.send('您还未登录,请先登录')
    }
});


app.listen(8002, () => {
    console.log('Server start. port=', 8002);
})

这里将数据存储到redis中。需要注意的是,sessionId对应的数据存储在redis中,通过req.session可以获取到所有数据。

结语

cookie和session多用于登录校验,在实际项目中,通常将登录信息等重要信息存放为session、其他信息如果需要保留,可以放在cookie中。

到这里基本已经介绍完了,只要你敲一遍上面的代码,我相信你肯定能够掌握cookie和session的概念以及怎么灵活地使用它们。