从单页应用看node的token(二)

上一篇中,讲了下cookie+session的方式check用户状态,可是处理CSRF(跨站请求伪造)上会麻烦一点。css

既然说到了CSRF,那先稍微解释一下。前端

CSRF

csrf是一种攻击方法,通俗的就是说攻击者假装成你进行行骗。 这种是如何作到的呢? 从原理上说,这种攻击方式不须要窃取到用户的cookie,而是直接使用用户的cookie去进行违法行为。vue

怎么作到的?

我模拟一下这种状况:node

  • 有一个网站A,A中有个post或get请求,用于对视频进行点赞。客户端和服务端的认证是用cookie进行的(参照第一篇的cookie+session的方式)
  • 如今有个网站B,是攻击者设置的,多是经过诱惑标题引你进去,抑或是经过经过xss方式往C网站植入了一个iframe,等等,不管哪一种设置,都是当你进入相应的B或C页面时,就会自动去请求A网站那个点赞的接口。
  • 若是咱们以前登录过网站A,而且并无登出,那么请求A网站那个接口时,浏览器就会用登录A网站时建立的cookie,去请求(这是浏览器自身特性)。好比B网站写了一个<img src="a网站/点赞接口/path"/>
  • 这时,可就等因而你在点赞了。

这可能会形成一个很大的问题,好比,会让某个视频资源点赞数很是高而顺序排的很是靠前, 或者是将一个资源标记为举报,致使资源下架。ios

还可能会由于网站的不严谨形成更严重的资金损失的问题,等等等等。web

到这里,你们可能会看到问题的严重性了。前端同窗由于以前不须要关注这些问题,当转到node时,也很容易忽略这类安全问题。express

如何防范

若是你们经过上面的例子知道了深层缘由,那也就有了大概的防范思路。可是咱们做为技术开发者,不能由于有这种问题而强制用户每次访问后都当即登出,这对用户是很是不友好的。json

对了,那咱们就不让居心不良的人使用cookie经过session让服务端肯定用户。也就是说,让其即便在B网站进行A网站的接口请求,A网站服务端并匹配不上用户,致使登陆不成功,那就没有问题了。axios

因此,token这种形式就特别适合了。浏览器

token

1. token 是什么

能够从网上搜一下,会看到不少的解释。我想通俗的说一下,就是在登陆成功时,咱们产生一段惟一的,不会轻易被解析的字符串,发送到客户端,客户端把这个字符串存起来,每次请求时把这段字符串带着,让服务端反解。

看到这里,是否是感受跟cookie+session的形式差很少? 是的,从原理上来讲,是差很少的,只是token咱们通常不把它放在cookie中,会放在好比loaclstorage中,即使坏人想用csrf的方式搞破坏,可是,他拿不到token,因此也就无法让服务端认证为登陆状态,他的阴谋也就没法得逞了。

2. 如何作?

第一,在node中生成好token

// 写一个中间件token-middleware.js
const setting = require('../../config/setting');
const verify = require('../../config/verify');

function tokenMiddleWare(req, res, next) {
    let token = req.headers[setting.token.header];
    if(token === undefined){
        return next();
    }else{
    	// 能够token校验并将校验结果保存至请求头中
        verify.getToken(token).then(data => {
            logger.info('校验的data是:::', data);
            req.data = data;
            return next();
        }).catch(err =>{
            logger.error('校验出现错误:', err);
            return next();
        })
    }
}
module.exports = tokenMiddleWare;
复制代码
//setting.js
module.exports = {
    token: {
    	// token密钥
        signKey: 'test_key_@@',
        // 过时时间300s
        signTime:  300,
        // 请求头参数
        header: 'authorization',
        // 不用校验的路由
        unRoute: [
            {url: /\.(jpg|png|css|js)$/, methods: ['GET']}
        ]
    }
}

复制代码
// verify.js
const jwt = require('jsonwebtoken');
const setting = require('./setting');

const verify = {
	// 设置token
    setToken(username, _id){
        return new Promise(resolve => {
            let token = jwt.sign(
            	// 存储数据,自定义
                {username, _id},
                // 密钥
                setting.token.signKey,
                {expiresIn: setting.token.signTime, algorithm: 'HS256'}
            );
            resolve(token);
        })
    },
    getToken(token){
        return new Promise((resolve, reject) => {
        	// 处理token字符串
            if(!token.split(' ').length){
                reject({error: 'The token value is empty'})
            }else{
            	// 解密token并返回数据
                let data = jwt.verify(token.split(' ')[1],setting.token.signKey)
                resolve(data)
            }
        })
    }
}

module.exports = verify;

复制代码

当登陆成功时,进行token的设置:

const verify = require('../../config/verify');

// loginInfo.username -> 登陆名
// loginInfo.passwd -> 登陆密码
verify.setToken(loginInfo.username, loginInfo.passwd).then(token => {
    // 生成token后,返回给客户端
    res.json({
        code: 0,
        mesg: 'success',
        token
    });
});

复制代码

而后,须要把写的中间件和express-jwt应用在app.js(你的根文件)中

const expressJwt = require('express-jwt');

// 加载token中间件
app.use(tokenMiddleware);
// 验证token是否可用
app.use(expressJwt({
    secret: setting.token.signKey,
    algorithms: ['HS256'],
    credentialsRequired: false, // 容许无token请求
    requestProperty: 'auth' // 把解析的值放在req.auth上
})
.unless({
    //除了这个path,其余的URL都须要验证
    path: setting.token.unRoute
}));


复制代码

注意,当使用express-jwt中间件时,须要一个兜底的中间件,来承接解析错误、token过时等结果。

若是出现错误,咱们默认返回401,因此咱们来设置一下。

app.use(function (err, req, res, next) {
    // 当验证token出现问题时,好比对不上,过时等状况,则返回401
    if (err.name === 'UnauthorizedError') {
      res.status(401).send(err.message);
    }
});

复制代码

这样node这一层就处理好了。 注意: 上面本身写的那个中间件是本身简单写的express-jwt功能,因此用express-jwt, 能够不用我写的那个中间件。

第二,客户端处理(使用vue)

客户端,首先要作的,就是保存服务端返回的token,我把它保存到了loaclstorage上。 好比:

res.data.token && localStorage.setItem('authToken', res.data.token);
复制代码

而后,须要处理每次前端向服务端的请求头:

// 一样,仍是用axios
// 拦截前端要发出去的请求
axios.interceptors.request.use(config => {
    let token = localStorage.getItem('authToken');
    if (token) {
        config.headers['Authorization'] = 'Bearer ' + token;
    }
    return config;
},
error => {
    console.error('拦截request出现错误', error);
});

复制代码

这样,每次前端的请求,都会带着这个Authorization头,node层拿到而且解析就能够了。

同时须要注意,若是token解析后,返回401,那么咱们也须要承接,而且转到登陆页面

axios.interceptors.response.use(response => {
     return response
    },
    error => {
        if(error.response.status === 401) {
            router.push('/login');
        }
    }
);

复制代码

token须要注意的问题

要想token不被csrf利用,前提是别让攻击者经过xss获取到,因此,须要处理好xss攻击。

总结

  • 跟cookie+session的基本原理很相近,都是处理http协议无状态的状况
  • token经过设置header头的形式,避开cookie,防止登陆相关的cookie被利用
  • 须要注意处理xss,xss是另一个攻击方式,但若是被xss了,token也就有危险了
  • token跟cookie并非谁替代谁的问题,而是在什么场景下用什么更为合适一些。

好了,关于登陆的时候涉及到的点和要规避的坑就先写到这。

但愿你们能有所收获,用1个多小时的看文章和实地开发测试,解决新手可能要3天才能研究透的问题。 若是你们喜欢,别忘了点个赞哈, 哈哈哈

相关文章
相关标签/搜索