preface

I’ve written an article that takes a deep dive into the context of cookies and sessions to help you understand how HTTP maintains Session state

One of the things that’s really important is, where do we store our Session on the server side? Today we are going to look at best practices for storing sessions

Session storage scheme

Store it in cookies

Storing sessions in cookies is a common approach that, while simple, has two fatal drawbacks:

  1. Session synchronization problem. Suppose we have two sites, a.hello.com b.hello.com respectively. If we want to share cookies among multiple sites, we basically set the Domain property of cookies to.hello.com. We completed login at A.hello.com, and automatic login will be performed when we enter B.hello.com. At this point, we change the session at a.hello.com (setting user permissions from 0 to 1) and the browser Cookie. After entering B.hello.com, the session of b.Hello.com server is old (user permission is 0), because the new session will resolve user permission is 1 according to the new Cookie, which conflicts with the original session content, for security reasons, The server will abandon the new session and continue to use the old one. As a result, session synchronization fails, causing problems

  2. Cookies have a limited size, typically 4KB. Any cookies that exceed the size limit are ignored and never set. If our state information exceeds 4KB, it will be lost

Stored in MongoDB

MongoDB is a document-based database where all data is read and written from disk. Document is equivalent to the record in MySQL, collection is equivalent to the table in MySQL, without writing SQL statements, using JSON for data storage

Stored in Redis

Redis is a memory-based key value database, which is implemented by C language. It is similar to the working principle of Nginx/NodeJS, and also works in a single-thread asynchronous way. It reads and writes memory first and then asynchronously synchronizes to disk, which has a huge improvement in read and write speed compared with MongoDB. As a result, many highly concurrent websites/applications currently use Redis as their cache layer, which is generally considered to be significantly better than MemoryCache. When concurrency reaches a certain level, consider using Redis to cache data and persist sessions

Both MongoDB and Redis can solve the shortcoming of Cookie storage, because after database storage, cookies are only responsible for storing keys, and all state information is stored in the same database. According to the keys, data in the database can be found, and then read, modify and delete accordingly

Obviously, Redis is a small and beautiful way to store persistent content with frequent read/write scenarios and CRUD operations like Session

Install Redis

Download: github.com/microsoftar…

If it is 64-bit, you can directly download the file marked in the red box. After downloading, create a new folder named Redis, unzip the downloaded ZIP package to the redis folder, and then move the folder to the root directory of drive C

cd c:\redis
redis-server.exe redis.windows.conf
Copy the code

At this point, open CMD again, do not close the original, enter the following command, we have completed the most basic key-value based data storage

cdC :\redis redis-cli.exe -h 127.0.0.1 -p 6379setToday the get 8.22 todayCopy the code

Start the Node application

Now to build our Node application, we use the current popular framework Koa2, we create a folder called Node-redis-session, and install the required NPM package

cd node-redis-session
npm init -y
npm i koa koa-session ioredis -S
Copy the code

Create app.js and index.html

We write the following code in index.html to simulate a user login


      
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>The login</title>
</head>

<body>
    <div>
        <label for="usr">Username:</label>
        <input type="text" name="usr" id="usr">
    </div>
    <div>
        <label for="psd">Password:</label>
        <input type="password" name="psd" id="psd">
    </div>
    <button type="button" id="login">login</button>
    <h1 id="data"></h1>
    
    <script>
        const login = document.getElementById('login');
        login.addEventListener('click'.function (e) {
            
            const usr = document.getElementById('usr').value;
            const psd = document.getElementById('psd').value;
            if(! usr || ! psd) {return;
            }
            // Use fetch to initiate a request
            const req = fetch('http://localhost:3000/login', {
                method: 'post'.body: `usr=${usr}&psd=${psd}`.headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            req.then(stream= >
                stream.text()
            ).then(res= > {
                document.getElementById('data').innerText = res; })})</script>
</body>

</html>
Copy the code

Back to app.js, we need to start a basic server, listen on port 3000, return the index.html static file, and do the logics related to the login service

const Koa = require('koa');
const fs = require('fs');
const app = new Koa();

app.use(async ctx => {
    if (ctx.request.url === '/') {
        // Returns the contents of index.html
        ctx.set({ 'Content-Type': 'text/html' });
        ctx.body = fs.readFileSync('./index.html');
        return;
    }
    if (ctx.url === '/login' && ctx.method === 'POST') {
        // Login logic processing......}}); app.listen(3000, () = > {console.log(`server is running at localhost:3000`);
});

Copy the code

Visit http://localhost:3000/ to see a basic login

Data for processing POST requests

Next, we need to parse the fetch request from the client, especially the body entity in the POST request. In index.html we have sent the username and password as a queryString, that is, usr=jack&psd=123

We wrote the parsePostData function in app.js and continued to refine our logon logic handling

// app.js
++ const qs = require('querystring');
++ function parsePostData(ctx) {
    // Return a Promise
    return new Promise((resolve, reject) = > {
        let data = ' ';
        // ctx.req is the NodeJS native request object 
      
       , we can use on('data'), get data
      
        ctx.req.on('data', chunk => {
            data += chunk;
        });
        // on('end'), data fetching is complete
        ctx.req.on('end', () => {
            data = data.toString();
            resolve(data);
        });
    });
}

app.use(async ctx => {
    ...
    if (ctx.url === '/login' && ctx.method === 'POST') {
        // Login logic processing......
        let postData = await parsePostData(ctx);
        // Use the QS module to convert queryString to an object
        postData = qs.parse(postData);
        console.log(postData)
    }
});
Copy the code

After filling in the user name and password, click login to view the printed record of the Node application. Haha! We have successfully obtained the data of body

Introducing Session processing

Let’s start by installing the NPM package for Koa2 to handle sessions

npm i koa-session -S
Copy the code

It is an easy-to-use session-specific Koa middleware that is implemented by default based on cookies and supports external storage

We introduce and configure it in app.js

// app.js
++ const session = require('koa-session');
++ const sessionConfig = {
    / / cookie key name
    key: 'koa:sess'.// The expiration time is one day
    maxAge: 86400000.// Do not sign
    signed: false}; ++ app.use(session(sessionConfig, app)); app.use(async ctx => {
    ...
    if (ctx.url === '/login' && ctx.method === 'POST') {
        // Login logic processing......
        let postData = await parsePostData(ctx);
        postData = qs.parse(postData);
        if (ctx.session.usr) {
            ctx.body = `hello, ${ctx.session.usr}`;
        } else {
            ctx.session = postData;
            ctx.body = 'you are first login'; }}});Copy the code

After filling in the relevant information, click Login and you can see the first login. It shows that you have logged in for the first time and set a Cookie in the browser

The reason why the value of Cookie is a string like garbled is that koA-session does encode processing on our data by default to prevent it from being read in plaintext

Click login again, and you can see that the server already owns our session and remembers our session state

Create RedisStore class

Down, down, down! At this time, we need our Redis to make a brilliant appearance. As mentioned above, KOA-Session provides an external storage interface. We only need to provide a Store class to realize the storage of session to the external database, perfect fit

The first step is to install an NPM package called ioredis, which encapsulates redis’ native operations and provides rich apis, much like Mongoose did with MongoDB

npm i ioredis -S
Copy the code

We create RedisStore in the root directory, RedisStore

const Redis = require('ioredis');
// Write a Store class that must have get() set() destory() methods
class RedisStore {
    constructor(redisConfig) {
        this.redis = new Redis(redisConfig);
    }
    async get(key) {
        const data = await this.redis.get(`SESSION:${key}`);
        return JSON.parse(data);
    }
    async set(key, sess, maxAge) {
        await this.redis.set(
            `SESSION:${key}`.JSON.stringify(sess),
            'EX',
            maxAge / 1000
        );
    }
    async destroy(key) {
        return await this.redis.del(`SESSION:${key}`); }}module.exports = RedisStore;
Copy the code

Now our Cookie no longer stores session information, only stores a Key, used to map the Key and Value in Redis are stored in Redis, and the security line and extensibility are greatly improved

We’ll also install an NPM package called Shortid, which will generate a shorter ID for our mapping Key

Meanwhile, we need to make corresponding changes in app.js

// app.js
++ const Store = require('./redis-store') + +const shortid = require('shortid');
++ const redisConfig = {
    redis: {
        port: 6379.host: 'localhost'.family: 4.password: '123456'.db: 0,}};const sessionConfig = {
    key: 'koa:sess'.maxAge: 86400000.signed: false.// Provide an external Store
    store: new Store(redisConfig),
    // The key generation function
    genid: (a)= > shortid.generate(),
};
Copy the code

Let’s empty the cookies and simulate logging in again

Click Login again to successfully implement external storage

keys *
get SESSION:hX_tSS8Od
Copy the code

Perfect query to the user information we stored before!

thinking

In the end, we successfully use Redis to solve the problem of Session storage in NodeJS, and achieve the three requirements of security, robustness and fast

In fact, Redis can also achieve many functions. As a cache intermediate layer, it can greatly optimize the performance of our application in some scenarios. For example, in the scenario where data is not frequently updated but is frequently read by users, data can be saved in Redis and returned through Node