CTF Rank网站开发笔记(五)——passport.js+geetest+bcrypt整合
难受了也是许多天了,今天总算是把前台都写完了,这次加上了登录注册的功能,用户名密码这次干脆激进一点密码采用了bcrypt加密存储。这里对整合过程做个记录。
首先是bcrypt,一般会采用在用户存储时进行加密,sequelize模型中建立hooks,使用beforeCreate方法对密码进行加密,并增加一个validPassword方法来进行比较:
"use strict";
var bcrypt = require('bcrypt');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('users', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
...(太多了,省略中间部分)
regtime: {
type: DataTypes.DATE,
allowNull: false
}
}, {
hooks:{
beforeCreate:function (user,options,next) {
bcrypt.hash(user.password,10,function(err,hash) {
user.password = hash;
next(null,user);
});
}
},
instanceMethods:{
validPassword:function(password) {
return bcrypt.compareSync(password,this.password);
}
},
tableName: 'users'
});
};
这里稍微记录一下bcrypt.hash其实有2个重载方法,以前老的写法通常是先调用bcrypt.genSalt()方法,然后再进行bcrypt.hash(password,salt,callback)进行运算,现在有了一个新方法bcrypt.hash(password,rounds,callback),直接输入运算轮数,算法会自动生成salt。
下面是bcrypt给出的参考计算时间:
rounds=8 : ~40 hashes/sec rounds=9 : ~20 hashes/sec rounds=10: ~10 hashes/sec rounds=11: ~5 hashes/sec rounds=12: 2-3 hashes/sec rounds=13: ~1 sec/hash rounds=14: ~1.5 sec/hash rounds=15: ~3 sec/hash rounds=25: ~1 hour/hash rounds=31: 2-3 days/hash
一般来说10 Rounds就已经很慢了,超过10Rounds就会影响正常用户使用,所以一般取10即可。
另外,通常情况下我们用的bcrypt是一个C++ library需要编译才能使用,现在好像还有个叫bcrypt-nodejs的库,是纯js实现的bcrypt,貌似现在比较推荐使用后一个,比较容易避免一些麻烦。
passport的整合:
passport是js实现的一个认证库,既可以本地认证,也可以进行OAuth的第三方认证。我这里直接用本地的认证就可以:
首先在app.js将passport引入:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
然后还需要定义serializeUser和deserializeUser方法用于认证用户信息的存取:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user) {
done(null, user);
},function (err){
done(err, false);
});
});
最后我们需要定义认证的方法:
passport.use(new LocalStrategy({
usernameField: 'username'
},
function(username, password, done) {
User.findOne({
where: {
username : username
}
}).then(function (user) {
if (!user) {
return done(null, false, { message: '用户名或密码错误' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: '用户名或密码错误' });
}
return done(null, user);
});
}
));
为了实现用户登录状态的保持,我们还需要express-session进行session的操作:
app.use(cookieParser('secret'));
app.use(session({
secret: 'secret',
name:'sid',
saveUninitialized: true,
resave: false,
cookie:{
maxAge:24*3600*1000 //session过期时间为1天
}
}));
然后进行初始化就可以使用了:
app.use(passport.initialize()); app.use(passport.session());
在用户认证的时候,使用passport.authenticate(‘local’,callback)(req,res,next);进行认证,其中callback就是前面定义的那个LocalStrategy里面定义的done函数,根据callback函数的参数就可以知道用户认证的结果了。当用户认证通过,可以调用req.logIn()函数进行登录操作。
利用locals注入用户信息:
由于req.logIn()成功后会在req.user中保存用户信息,但这个信息是不能直接在ejs模板中取出的,需要从controller的res.render()方法传入,我们肯定不打算每个页面都去传这个值,这时候可以用res.locals来解决,在所有router之前加入:
app.use(function(req,res,next){
res.locals.user=req.user; //为每个页面注入登录信息
next();
});
这时候req.user就注入到res返回给模板的的user变量中去了。
整合geetest:
geetest是个不错的第三方验证码框架,使用还算方便,这次就打算用一下。
对于后端,只需copy&paste官方的demo即可:
myutils.pcGeetest = new Geetest({
geetest_id: 'appid',
geetest_key: 'appkey'
});
router.get('/register', function (req,res) {
// 向极验申请每次验证所需的challenge
myutils.pcGeetest.register(function (err, data) {
if (err) {
// 进入failback,如果一直进入此模式,请检查服务器到极验服务器是否可访问
// 可以通过修改hosts把极验服务器api.geetest.com指到不可访问的地址
// 为以防万一,你可以选择以下两种方式之一:
// 1. 继续使用极验提供的failback备用方案
res.send(data);
// 2. 使用自己提供的备用方案
// todo
} else {
// 正常模式
res.send(data);
}
});
});
router.post("/validate",function (req,res) {
// 对ajax提供的验证凭证进行二次验证
myutils.pcGeetest.validate({
challenge: req.body.geetest_challenge,
validate: req.body.geetest_validate,
seccode: req.body.geetest_seccode
}, function (err, success) {
if (err) {
// 网络错误
res.send({
status: "error",
info: err
});
} else if (!success) {
// 二次验证失败
res.send({
status: "fail",
info: '登录失败'
});
} else {
res.send({
status: "success",
info: '登录成功'
});
}
});
});
然后在页面里插入geetest的js即可:
DOM中插入:
<div id="float-captcha"></div>
js脚本:
var handlerFloat = function (captchaObj) {
$("#float-submit").click(function (e) {
var validate = captchaObj.getValidate();
if (!validate) {
$("#notice").removeClass("hide");
$("#notice").addClass("show");
setTimeout(function () {
$("#notice").removeClass("show");
$("#notice").addClass("hide");
}, 2000);
e.preventDefault();
}
})
// 将验证码加到id为captcha的元素里,同时会有三个input的值:geetest_challenge, geetest_validate, geetest_seccode
captchaObj.appendTo("#float-captcha");
captchaObj.onReady(function () {
$("#wait").removeClass("show");
$("#wait").addClass("hide");
});
// 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
};
$.ajax({
// 获取id,challenge,success(是否启用failback)
url: "/geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
success: function (data) {
// 使用initGeetest接口
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
initGeetest({
gt: data.gt,
challenge: data.challenge,
product: "float", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
// 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
}, handlerFloat);
}
});
这样,在form提交后会插入geetest的几个field字段,后端收到后还需要提交给geetest服务器进行再次验证:
myutils.pcGeetest.validate({
challenge: req.body.geetest_challenge,
validate: req.body.geetest_validate,
seccode: req.body.geetest_seccode
}, function (err, success) {
if (err) {
// 网络错误
res.send(err);
} else if (!success) {
// 二次验证失败
} else {
//验证成功
}
});
只有前端+后端校验都无误,才算通过。
最后补充下,以前在express 3.x的时候,获取node环境变量是用process.env.NODE_ENV来获取的,而现在express 4.x继续这样做已经不行了,而应该用req.app.get(‘env’)来获取,这一点还是坑了我有点久,而现在NODE_ENV的环境变量,如果不设置那默认就是development,所以在部署的时候注意要set NODE_ENV=production,不然有些错误可能会回显给用户,造成某些不安全因素。
以上就是这次踩坑的过程,记录下过程以方便以后开发。
浙公网安备 33011002014706号
西瓜
啊啊在微博上看到的,本来想找找到底是哪儿的神器,google了下没发现却发现了这篇文章,然后又跑去博主的github找了圈,看起来是正在上锁开发中呀~~超级期待的~加油!