上一次筆者寫了一篇關於安全的文章:【Web安全:細說前端XSS攻擊與防範】投石問路,效果還不錯,深受鼓舞。javascript
其實和web相關的安全問題不只於此,大體有【XSS攻擊】、【前端CSRF攻擊】、【前端cookies問題】、【前端點擊劫持問題】、【HTTP傳輸安全】、【DOS攻擊】、【後端密碼安全】、【SQL注入】等一系列問題,慢慢給你們總結出來吧。。。
(爲何這麼多「前端…」?emmmmmmm,多是怕攻擊頻繁,對服務器形成隱患吧。但其實這些前端/後端的防護措施通常都要和後端/前端結合起來纔可以更有效的運行)html
今天就和各位嘮嘮「後端密碼安全」,簡單說說(其實說是沒啥說的…),本文代碼主要適用於中小型項目,若有不當之處,還請之處嘞。前端
密碼安全兩三事
如今通常一提起密碼安全,就會想到這些年「大火的」數據庫泄露 —— 事實上,這也確實是如今密碼安全防範的一個重點:
泄露渠道:java
- 數據庫被「偷」
- 服務器被入侵
- 通信被竊聽
- 內部人員泄露數據
- 撞庫
有了早些年處於熱搜的「CSDN幾百萬用戶數據泄露」、「京東帳號密碼數據庫泄露」、「12306數據庫泄露」事件,業界終於認識到數據庫的「不太安全性」,以及作出了一些基本的防護措施,好比:node
- 嚴禁密碼明文存儲(防泄露)
- 單向變換(防泄露)
- 密碼自己複雜度要求(防猜解)
- 「加鹽」(防猜解)
後來作了多少努力咱暫且不提,可是如今所說密碼安全的防護措施,大多數人應該都下意識想到哈希算法,它有這樣幾個特色:mysql
- 明文-密文一一對應
- 雪崩效應:只要有一點不對應,則後面全部的都不對應
- 密文沒法反推到明文
哈希算法常見有:MD五、SHA一、SHA256…web
說到這就會有人問了:第三個特色怎麼體現?如今有好多這種MD5反推的網站耶。。。
那是對於簡單一些的明文:好比123456之類的。由於他們用的是暴力破解(創建了數據字典 —— 也就是咱們常說的「彩虹表」)。
可想而知,對於一些很是複雜的密碼,並且甚至於加密了好屢次,那破解幾乎就無從談起了(字典存儲是須要空間的,查字典也是要時間的):算法
表達式 | 難易程度 |
---|---|
md5(明文)=密文 | 易 |
md5(md5(明文))=密文 | 易 |
md5(md5(md5(明文)))=密文 | 稍難 |
md5(sha1(明文))=密文 | 稍難 |
md5(md5(md5(明文+一個複雜字符串)))=密文 | 難 |
md5(sha256(sha1(明文)))=密文 | 難 |
經驗:用足夠複雜的密碼對抗彩虹表,但儘可能不要讓用戶「承擔」這件事。sql
密碼安全實戰片斷
這些天剛好筆者寫了一個相似每日新聞的項目,若是作大的話,這種項目的登陸註冊必需要進行簽名/加密,不然在評論區可能會有「意想不到的」錯誤。
(前端代碼省略,主要來看一下後端代碼中關於登陸密碼的片斷)
這裏筆者用的是node.js,前端用的node模塊——ejs,簽名所用md5模塊:
數據庫
lib文件夾下的common.js文件:
//md5的封裝 const crypto=require('crypto'); module.exports={ MD5_SUFFIX:'ao8durq34fb3($&896359lhd8q是奧迪會36412#', md5:function(str){ var obj=crypto.createHash('md5'); //以MD5格式簽名 obj.update(str); return obj.digest('hex'); //hex:16進制(輸出格式) } };
這個文件就是對MD5模塊的簡單封裝,MD5_SUFFIX這個變量就是上文所說的那個爲了增長複雜度而在明文後加的「一個很複雜的字符串」。
crypto模塊爲node中的MD5模塊所在模塊,有不明白的請移步node官網
這裏咱們再簡化一下場景:假如這個項目中有個【管理員】,它的帳號和密碼是固定的——
咱們能夠經過MD5將「(原定)明文密碼」進行簽名,而後將簽名後的密碼字符串添加到數據庫對應字段裏。在用戶以【管理員】帳號登陸時,對用戶輸入字符串(明文)進行MD5加密,而後比對數據庫中字符串,便可。
項目>md5_2.js:
const common=require('./lib/common'); var str='123456'; var str2=common.md5(str+'ao8durq34fb3($&896359lhd8q是奧迪會36412#'); //或者:str+common.MD5_SUFFIX; console.log(str2);
總之,str+後面的字符串必定要跟上面common文件裏的屬性變量MD5_SUFFIX一致。
而後就到了最重要的部分了:驗證!
這裏須要注意一點的是:若是是用post提交數據的話,後端必定要用post接收數據,不然get請求不會被響應,打印出來數據就是undefined了。
route>admin.js:
const express=require('express'); const common=require('../lib/common'); const mysql=require('mysql'); //node鏈接mysql var db=mysql.createPool({ host:'localhost',user:'root',password:'root',database:'xxx'}); module.exports=function(){ var router=express.Router() router.use((req,res,next)=>{ if(!req.session['admin_id'] && req.url!='/login'){ res.redirect('/admin/login'); }else{ next(); } }); router.get('/login',(req,res)=>{ res.render('admin/login.ejs',{ }); }); router.post('/login',(req,res)=>{ var username=req.body.username; var pass=common.md5(req.body.password+common.MD5_SUFFIX); //node操做mysql語句 db.query(`SELECT * FROM XXX WHERE USERNAME='${ username}'`,(err,data)=>{ if(err){ res.status(500).send('databases error').end(); }else{ if(data.length==0){ res.status(400).send('empty databases').end(); }else{ if(data[0].password==pass){ req.session['admin_id']=data[0].ID; res.redirect('/admin/'); }else{ res.status(400).send('密碼錯誤').end(); } } } }) }); router.get('/',(req,res)=>{ res.send('進入首頁了').end(); }) return router; }
這裏有幾個比較重要的點:
- req.body——這裏使用的body-parser中間件(在主文件中),這個中間件可讓post數據以請求體的形式傳到後端
- 第八行以use起頭——use實際上是get和post的「合體」,經常使用在不肯定請求會以哪一種形式發送時使用,這裏用use並且不明確【監聽路由】,意味着:不管走到哪裏,都要通過這個路由監聽函數
- 若是是get進來(/login)的,說明什麼數據都沒有攜帶,就渲染出登陸的模板,若是有數據(第二次進這個網站或者提交數據後)就去判斷
這個文件最後將router暴露了出去 —— 它固然要被引用,但這裏只是登陸設置,一樣的,在一個項目中還有其餘各類模塊…因此咱們採用「二級路由」的設置方式:
主文件server.js:
//... //1.獲取請求數據 server.use(bodyParser.urlencoded()); server.use(multerObj.any()); //2.cookie、session //3.模板 //4.router server.use('/',require('./web.js')()); //其餘頁面 server.use('/admin/',require('./route/admin.js')()); //5.default——static //...
本文所說爲express中籤名模塊crypto的應用。
而在另外一個新興框架koa中,有一個更爲便捷的插件:bcryptjs:
「加密」時只需調用:require("bcryptjs").hashSync('須要加密的變量參數',5)
—— 以級別5進行加密
用戶輸入密碼後對比時只需調用:require("bcryptjs").compareSync('提交的密碼','數據庫查詢到的密碼')
本文同步分享在 博客「行舟客」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。