Web安全:細說後端密碼安全防範

上一次筆者寫了一篇關於安全的文章:【Web安全:細說前端XSS攻擊與防範】投石問路,效果還不錯,深受鼓舞。javascript

其實和web相關的安全問題不只於此,大體有【XSS攻擊】、【前端CSRF攻擊】、【前端cookies問題】、【前端點擊劫持問題】、【HTTP傳輸安全】、【DOS攻擊】、【後端密碼安全】、【SQL注入】等一系列問題,慢慢給你們總結出來吧。。。
(爲何這麼多「前端…」?emmmmmmm,多是怕攻擊頻繁,對服務器形成隱患吧。但其實這些前端/後端的防護措施通常都要和後端/前端結合起來纔可以更有效的運行)html

今天就和各位嘮嘮「後端密碼安全」,簡單說說(其實說是沒啥說的…),本文代碼主要適用於中小型項目,若有不當之處,還請之處嘞。前端

fj

密碼安全兩三事

如今通常一提起密碼安全,就會想到這些年「大火的」數據庫泄露 —— 事實上,這也確實是如今密碼安全防範的一個重點:
泄露渠道:java

  • 數據庫被「偷」
  • 服務器被入侵
  • 通信被竊聽
  • 內部人員泄露數據
  • 撞庫

有了早些年處於熱搜的「CSDN幾百萬用戶數據泄露」、「京東帳號密碼數據庫泄露」、「12306數據庫泄露」事件,業界終於認識到數據庫的「不太安全性」,以及作出了一些基本的防護措施,好比:node

  • 嚴禁密碼明文存儲(防泄露)
  • 單向變換(防泄露)
  • 密碼自己複雜度要求(防猜解)
  • 「加鹽」(防猜解)

後來作了多少努力咱暫且不提,可是如今所說密碼安全的防護措施,大多數人應該都下意識想到哈希算法,它有這樣幾個特色:mysql

  1. 明文-密文一一對應
  2. 雪崩效應:只要有一點不對應,則後面全部的都不對應
  3. 密文沒法反推到明文

哈希算法常見有:MD五、SHA一、SHA256…web

說到這就會有人問了:第三個特色怎麼體現?如今有好多這種MD5反推的網站耶。。。
那是對於簡單一些的明文:好比123456之類的。由於他們用的是暴力破解(創建了數據字典 —— 也就是咱們常說的「彩虹表」)。
可想而知,對於一些很是複雜的密碼,並且甚至於加密了好屢次,那破解幾乎就無從談起了(字典存儲是須要空間的,查字典也是要時間的):算法

表達式 難易程度
md5(明文)=密文
md5(md5(明文))=密文
md5(md5(md5(明文)))=密文 稍難
md5(sha1(明文))=密文 稍難
md5(md5(md5(明文+一個複雜字符串)))=密文
md5(sha256(sha1(明文)))=密文

經驗:用足夠複雜的密碼對抗彩虹表,但儘可能不要讓用戶「承擔」這件事。sql

fj

密碼安全實戰片斷

這些天剛好筆者寫了一個相似每日新聞的項目,若是作大的話,這種項目的登陸註冊必需要進行簽名/加密,不然在評論區可能會有「意想不到的」錯誤。
(前端代碼省略,主要來看一下後端代碼中關於登陸密碼的片斷)
這裏筆者用的是node.js,前端用的node模塊——ejs,簽名所用md5模塊:
ml數據庫

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一致。
1


而後就到了最重要的部分了:驗證!
這裏須要注意一點的是:若是是用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;
}

這裏有幾個比較重要的點:

  1. req.body——這裏使用的body-parser中間件(在主文件中),這個中間件可讓post數據以請求體的形式傳到後端
  2. 第八行以use起頭——use實際上是get和post的「合體」,經常使用在不肯定請求會以哪一種形式發送時使用,這裏用use並且不明確【監聽路由】,意味着:不管走到哪裏,都要通過這個路由監聽函數
  3. 若是是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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索