github.com/iNuanfeng/b…
preface
Session, also known as Session control, stores properties and configuration information required by a specific user’s Session. Stored on the server and persists throughout the user session.
However:
- What exactly is a session?
- Is the session stored in server memory, or is it supported natively by the Web server?
- HTTP requests are stateless, so why does the server get your session every time?
- Does closing the browser expire?
This article will explain the mechanism of session in detail from the source code of KOA-Session (session middleware officially maintained by KOA). Hopefully, after reading this, you’ll have a better understanding of the nature of sessions and the difference between sessions and cookies.
Basic knowledge of
I’m sure you all know a little bit about cookies and sessions. The most common interpretation is that cookies are stored in the browser and sessions are stored in the server.
Cookies are supported by browsers, and HTTP requests carry cookies to the server in the request header. That is, every time the browser visits a page, the server gets a cookie from that visitor.
However, as for where the session is stored on the server and how the server corresponds to the session of this visitor, I actually asked some backend students and gave vague explanations. Because the service framework is generally built-in with this function, are directly used. What is the principle behind it, do not necessarily pay attention to.
If we have used the KOA framework, we know that KOA itself cannot use sessions, which seems to indicate that sessions are not supported natively by the server and must be supported by the KOA-Session middleware.
That it is exactly how to implement the mechanism, then we will enter the source code interpretation.
The source code interpretation
Koa-session:github.com/koajs/sessi…
Interested students can download the code and have a look at it first
The code posted in the process of interpretation has been simplified in part
Koa – structure of the session
Look at the directory structure of koA-session, very simple; The main logic is focused on context.js.
├ ─ ─ index. Js / / entrance ├ ─ ─ lib │ ├ ─ ─ the context, js │ ├ ─ ─ the session. The js │ └ ─ ─ util. Js └ ─ ─ package. The jsonCopy the code
First, give a brain map of the main modules of KOA-session.
Repeat the process
Let’s take a step-by-step look at the koA-session initialization process:
Let’s take a look at how koa-Sessin is used:
const session = require('koa-session');
const Koa = require('koa');
const app = new Koa();
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa:sess'.// Default value, custom cookie key
maxAge: 86400000
};
app.use(session(CONFIG, app)); // Initialize koA-session middleware
app.use(ctx= > {
let n = ctx.session.views || 0; // The session of the current user can be fetched each time
ctx.session.views = ++n;
ctx.body = n + ' views';
});
app.listen(3000);
Copy the code
Initialize the
When koA-Session is initialized, an app instance is required.
In fact, it is at initialization time that the session object is mounted to app.context and is instantiated from lib/context.js. So the ctx.session we use is a class constructed by KOa-session itself.
We open koa-session/index.js:
module.exports = function(opts, app) {
opts = formatOpts(opts); // Format configuration items to set some default values
extendContext(app.context, opts); // Define session object for app.ctx
return async function session(ctx, next) {
const sess = ctx[CONTEXT_SESSION];
if (sess.store) await sess.initFromExternal();
await next();
if (opts.autoCommit) {
awaitsess.commit(); }}; };Copy the code
Returns a KOA middleware function through an internal initialization.
Step by step, formatOpts is used to do some default parameter processing. The main task of extendContext is to do an interceptor for CTX, as follows:
function extendContext(context, opts) {
Object.defineProperties(context, {
[CONTEXT_SESSION]: {
get() {
if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION];
this[_CONTEXT_SESSION] = new ContextSession(this, opts);
return this[_CONTEXT_SESSION]; }},session: {
get() {
return this[CONTEXT_SESSION].get();
},
set(val) {
this[CONTEXT_SESSION].set(val);
},
configurable: true,}}); }Copy the code
CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION: CONTEXT_SESSION There are methods to initialize it (initFromExternal, initFromCookie). It then mounts a “public” Session object.
Why are we talking about “private” and “public”? Here’s the details. The Symbol type is used to make CTX [CONTEXT_SESSION] inaccessible. The (get/set) method is exposed only through ctx.session.
Take a look at the middleware functions that index.js exports
return async function session(ctx, next) {
const sess = ctx[CONTEXT_SESSION];
if (sess.store) await sess.initFromExternal();
await next();
if (opts.autoCommit) {
awaitsess.commit(); }};Copy the code
Here, CTX [CONTEXT_SESSION] instance is assigned to sess, and sess.initFromExternal is called, depending on whether opts.store is present. Call an external thing to initialize the session, which we’ll talk about later.
Next, we execute the following code, which executes our business logic.
await next()
Copy the code
And then there’s this, which looks like something like saving a session.
sess.commit();
Copy the code
After the above code analysis, we saw the main flow and save operation of the KOA-Session middleware.
So when is the session created? Going back to the interceptor extendContext mentioned above, it instantiates the Session object from the ContextSession class upon receiving the HTTP request.
That is, sessions are created and managed by the middleware itself, not by the Web server.
Let’s move on to the core function, ContextSession.
ContextSession class
Let’s start with the constructor:
constructor(ctx, opts) {
this.ctx = ctx;
this.app = ctx.app;
this.opts = Object.assign({}, opts);
this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store;
}
Copy the code
I can’t believe I didn’t do anything. Look down at the get() method:
get() {
const session = this.session;
// already retrieved
if (session) return session;
// unset
if (session === false) return null;
// cookie session store
if (!this.store) this.initFromCookie();
return this.session;
}
Copy the code
Oh, it is a singleton pattern (wait until it is used to regenerate the object, and multiple calls directly use the first object).
There is a check to see if the opts.store argument is passed, and if not, initFromCookie() is used to generate the session object.
What if you upload opts.store and do nothing WTF?
Obviously not, remember the initFromExternal function call.
if (sess.store) await sess.initFromExternal();
Copy the code
So, there are two different ways to generate sessions depending on whether there is opts.store.
Q: What is a store?
A: The store can be seen in initFromExternal, which is actually an external store.
Q: What external storage and where?
Answer: the classmate is not nasty, look back first.
initFromCookie
initFromCookie() {
const ctx = this.ctx;
const opts = this.opts;
const cookie = ctx.cookies.get(opts.key, opts);
if(! cookie) {this.create();
return;
}
let json = opts.decode(cookie); // If you print json, you will find that it is your session object!
if (!this.valid(json)) { // Determine cookie expiration
this.create();
return;
}
this.create(json);
}
Copy the code
Here, we find an important piece of information, the session is encrypted and stored directly in the cookie.
Let’s console.log the JSON variable to verify:
initFromeExternal
async initFromExternal() {
const ctx = this.ctx;
const opts = this.opts;
let externalKey;
if (opts.externalKey) {
externalKey = opts.externalKey.get(ctx);
} else {
externalKey = ctx.cookies.get(opts.key, opts);
}
if(! externalKey) {// create a new `externalKey`
this.create();
return;
}
const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling });
if (!this.valid(json, externalKey)) {
// create a new `externalKey`
this.create();
return;
}
// create with original `externalKey`
this.create(json, externalKey);
}
Copy the code
You can see that store.get() has a string of information stored in the store that can be gotten.
And it keeps calling create().
create
So what does create() do?
create(val, externalKey) {
if (this.store) this.externalKey = externalKey || this.opts.genid();
this.session = new Session(this, val);
}
Copy the code
It determines store, and if there’s a store, it sets externalKey, or it generates a random ID.
Sotre stores some information, which can be accessed via externalKey.
This basically implies that sessions are not supported natively by the server, but are created and managed by the Web services themselves.
Where to store it? It doesn’t have to be on the server, it can be in a cookie like a KOa-session!
Let’s move on to the last Session class.
The Session class
As usual, look at the constructor first:
constructor(sessionContext, obj) {
this._sessCtx = sessionContext;
this._ctx = sessionContext.ctx;
if(! obj) {this.isNew = true;
} else {
for (const k in obj) {
// restore maxAge from store
if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge;
else if (k === '_session') this._ctx.sessionOptions.maxAge = 'session';
else this[k] = obj[k]; }}}Copy the code
Received ContextSession instance from sessionContext and OBj, nothing else.
The Session class is simply used to store the value of the Session and _maxAge, and it provides a toJSON method to get the value of the Session object that filters _maxAge and other fields.
How does session persist
Now that we know that a session can be taken from an external source or from a cookie, how is it saved? Go back to the commit method in koa-session/index.js and see:
await next();
if (opts.autoCommit) {
await sess.commit();
}
Copy the code
The idea is immediately clear: after the middleware finishes next(), it makes a commit().
The commit() method, which can be found in lib/context.js:
async commit() {
/ /... Omit n judgments, including whether there is a change and whether the session needs to be deleted
await this.save(changed);
}
Copy the code
Look again at the save() method:
async save(changed) {
const opts = this.opts;
const key = opts.key;
const externalKey = this.externalKey;
let json = this.session.toJSON();
// save to external store
if (externalKey) {
await this.store.set(externalKey, json, maxAge, {
changed,
rolling: opts.rolling,
});
if (opts.externalKey) {
opts.externalKey.set(this.ctx, externalKey);
} else {
this.ctx.cookies.set(key, externalKey, opts);
}
return;
}
json = opts.encode(json);
this.ctx.cookies.set(key, json, opts);
}
Copy the code
So what you’re doing is you’re essentially stuffing json data into a cookie by default, which is storing encrypted session information.
Then, if an external store is set, store.set() is called to save the session. The specific store logic, where to save, is up to the Store object itself!
summary
The koA-session approach shows that a session is just an object that can be stored in cookies or anywhere (memory, database). It’s up to the developer to decide where to store it, as long as you implement a store object that provides set and get methods.
extending
Through the above source code analysis, we have got the answer to the question at the beginning of our article.
What else is worth thinking about in KOA-Session?
Plug-in design
I have to say, the Store’s plug-in design is excellent. Koa-session does not care how the data is stored, as long as the plug-in provides the access method it needs.
This plug-in architecture reverses dependencies between modules, making KOA-Session easy to extend.
Security considerations for KOA-session
This default of storing user information in cookies is always insecure.
So now we know we need to do something else with it. For example, implement their own store, save sessions to Redis and so on.
What is the difference between this session login method and token
This is actually from the use of token, the use will be more flexible, here is not to say.
The principle and comparison of various login strategies will be written later. Interested students can follow me.
conclusion
Looking back at the first few questions of this article, we already have clear answers.
- Session is a concept, a data object that stores information about visitors.
- Session storage is defined by the developer and can be stored in memory, Redis, mysql or even cookies.
- When a user visits for the first time, we create a session for the user and insert his “key key” into the cookie. So even though the HTTP request is stateless, we can retrieve the visitor’s “key key” through cookies, and then retrieve the corresponding visitor’s session from the session collection of all visitors.
- The server session does not immediately expire when the browser is closed. The session middleware implements a set of management methods by itself. When the access interval exceeds maxAge, the session will expire.
Is there any other way to implement user logins besides koA-session?
In fact, there are many, can store cookie implementation, can also use token way. In addition, there are single sign-on, third-party login and so on. If you are interested, you can continue to give you analysis in the following article.