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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索