從單頁應用看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天才能研究透的問題。 若是你們喜歡,別忘了點個贊哈, 哈哈哈

相關文章
相關標籤/搜索