从单页应用的登陆功能看node的cookie和session处理(1)

在开发一个网站时,绝大多数数状况都会用到登陆、自动登陆功能。本篇是第一篇,用cookie和session来记录用户数据。html

你们都知道http协议是无状态的,也就是,从这个页面到另外一个页面,服务端是不知道是否为同一用户的。因此,慢慢发展成了前端用cookie保存sessionID,后端使用session进行加密及解密的一套方法。前端

1、cookie和session整体思路

在这里我稍微讲一下cookie和session是如何合做的,咱们前端人员刚接触node开发时,思惟多是须要进行转化的,可能转化须要一段时间。可是当你搞明白了之后,成为全栈工程师后,就会对整个流程了然于胸了。这里使用express来说解vue

首先,明确要记录用户,cookie里应该要存什么。注意,cookie里尽可能不要存储密码和用户名,一是有被窃取的危险,而是增长cookie的大小。(固然并非说必定不能这么作,好比把用户名和密码进行md5等方式处理,也相对会很安全)cookie里存储的是sessionID,那这个sessionID是如何跟服务端进行交互的,后面详细讲。node

express-session是处理session的中间件。这个中间件若是阅读下源码,就会看到,主要是进行了两件事:

(1)请求体中没有cookie的时候

用户的请求过来之后,若是在请求体中没有cookie,则建立一个新的session,而且建立一个不会重复的sessionID, 把seesionID和session在store里进行关联。(store默认是内存)ios

MemoryStore.prototype.set = function set(sessionId, session, callback) {
  this.sessions[sessionId] = JSON.stringify(session)
  callback && defer(callback)
}

复制代码

当setCookie的时候,把注册session中间件中的option进行处理:vue-router

具体是把option中的name, secret跟生成的sessionID进行签名加密,设置res.setHeader('Set-Cookie', header) 把加密后的结果传到客户端。数据库

(2)当再次访问页面的时候

这时,cookie就会随请求头到了服务端, 那么express-session就会对cookie进行解密,把sessionID解出来,而后经过这个id,就找到了store中存储的session,也就拿到session中的用户数据,好比username等(前提是在登陆时,把username存到了session上面,好比req.ression.username = user)。express

这时,服务端就知道访问者是谁了,就能够把属于这个访问者的数据返回到页面中。json

2、真实项目开发

上面讲解了总体流程,虽然没有展开讲,但我理解能够帮助你们抓到总体脉络。axios

好了,下面开始展开讲代码。具体的各个key是什么含义,我就不讲了,具体可看文档。

express-session是个中间件,那么就须要把这个中间件加载到express程序中。

const session = require('express-session');
app.use(session({
    secret: 'test-node-session',
    resave: false,
    saveUninitialized: true,
    cookie: {
        path: '/',
        httpOnly: true,
        secure: false,
        // 若是不设置maxAge,则退出关闭浏览器tab,当前cookie就会过时
        //maxAge: 1000 * 60 
    }
}));

复制代码

经过上面设置,当一个请求来了之后,若是以前页面没有对应的cookie,则产生一个新的cookie,若是有,会进行更新后从新发送到浏览器中。

固然,页面请求后就会产生这个name为connect.sid的cookie。 这时咱们进行登陆:

登陆时,经过post请求,把数据和cookie都给到了服务端。

app.post('/author/gologin', (req, res) => {
    // 经过req.body拿到用户名和密码、是否自动登陆
    ...
    // 去数据库里进行匹配,若是匹配到,说明登陆成功
    ....
    // 这时,能够把用户名存储到session中,
    req.session.user = req.body.username;
    // 由于咱们是单页应用,因此返回json,让前端路由到对应的页面
    res.json({
        code: 0,
        mesg: ''
    });
})

复制代码

前端页面拿到请求的返回值,决定路由到哪一个页面。

// 使用vue
axios.post('/author/gologin', {
        data: JSON.stringify(this.accountFormItem)
    })
    .then(res => {
        console.log('登陆帐号返回数据:', JSON.stringify(res));
        switch (+res.data.code) {
            case 0:
                this.$router.push('/go/to/path/list');
                break;
            case 2004:
                this.inputError = true;
                break;
            default:
                break;
        }
    })
    .catch(err => {
        console.error('登陆帐号出现错误:', err);
    });

复制代码

这时,vue-router会进行导航,到了对应的页面。 当到了新的页面后,好比到了列表页,那就会涉及去访问列表的异步接口,

这里注意,应该在全部的异步请求中去判断一下用户是否存在,也就是先查一下用户是否已经登陆。好比客户端发送了一个get请求 '/get/total/list', 到了服务端,进行处理:

// node进行处理
app.get('/get/total/list', (req, res) => {
    if (!req.session.user) {
        return res.status(401).send({msg: 'Unauthorize'});
    }
    // 去数据库里查数据
    ...
    // 把数据返回给客户端
    return res.json({code: 0, data: result});
})

复制代码

在上面的代码中,有很重要的一句话,那就是若有找到req.session.user,则继续流程,若是没有找到,就返回401状态,也就是未认证,这个状态是符合http语义的。

那么服务端返回这个状态,客户端须要拿到而且路由到登陆页。这种状况通常不会出现,可是要处理,好比说有人更改了cookie的时候,就匹配不到用户数据了。

// 客户端使用axios,能够对数据的返回进行拦截,在单页的app.js页面
axios.interceptors.response.use(response => {
     // 查看返回的状态
     return response
    },
    error => {
        if(error.response.status === 401) {
            router.push('/login');
        }
    }
);

复制代码

这时页面回到了登陆页,要求用户从新登陆。

上面就是整个过程了。固然,上面仍是个比较粗略的实现,没有包含用户直接刷新页面的状况。下面稍微提一下。

好比当用户直接刷新列表页,咱们知道,单页应用只有一个html入口文件,好比默认是index.html,那么过程就是须要把index.html返回到浏览器,浏览器根据路由渲染对应的页面,而后进行页面内容的异步请求。(具体如何作到,请看我对单页应用在node中运行起来的相关文章)

这里有个体验问题: 若是用户在这时把cookie删除了,那么页面会先进行上面说的步骤,最后才返回401,而后再跳转到登陆页,会有一个很明显的闪烁过程,先把列表页加载了出来,又跳到了登陆页,用户体验很差。

那么咱们能够作些优化。

优化点

  • 在登陆的时候,能够把用户数据,好比用户名保存在浏览器的sessionStorage中,当刷新页面时,先经过vue的路由守卫来判断有没有用户名,若是没有,则直接跳到登陆页,就不须要通过上面复杂而又难看的流程中去。
  • sessionStorage不太须要担忧泄漏,由于只要xss作好过滤,别有用心的人拿不到,而且当关闭浏览器时,会自动清空。
  • 当用户关闭浏览器后,又打开,这时,由于sessionStorage已经没有了,因此,须要在路由守卫中异步请求一下,看下用户是否还在登陆状态(好比7天自动登陆),进行处理。
// vue 全局路由守卫
router.beforeEach((to, from, next) => {
    function getRouteEach(to, next) {
        if (sessionStorage.getItem('testuser')) {
            // 若是url是登陆页,则路由到内容页
            if (to.path === '/login') {
                return next({path: '/to/the/path/list'});
            }
            next();
        } else {
            // 若是发现没有登陆,则去请求一次登陆状态
            axios.get('/api/getsid')
            .then(res => {
                // 服务端有登陆状态
                if (res.data.code === 0) {
                    sessionStorage.setItem('testuser', res.data.data.sessionName);
                    // 路由到对应页面
                    return getRouteEach(to, next);
                } else if (to.path !== '/login') {
                    // 若是确认没有登陆,则跳到登陆页面
                    return next({path:'/login'});
                }
                next();
            })
            .catch(e => {
                console.error('查看sid失败:', e);
            })
        }
    }
    getRouteEach(to, next);
    
});

复制代码

总结

  • node异步请求,check一下用户是否真的已经登陆。
  • 经过 axios的全局监听返回状态,拦截401,并路由到登陆页。
  • 经过vue的路由守卫,优化用户体验。

安全性

你们都知道,很长一段时间内,你们都用这种方式来处理,可是这里有个问题,就是在防csrf攻击(跨站请求伪造)的时候,就容易防不住了。固然能够用好比双cookie认证等方式增强,可是下一篇我要讲的token方式更为方便。<<从单页应用看node的token>> 欢迎你们继续关注~~

相关文章
相关标签/搜索