nodeJS---express4+passport實現用戶注冊登錄驗證


網上有很多關於passport模塊使用方法的介紹,不過基本上都是基於express3的,本文介紹在express4框架中使用passport模塊。

前言

passport是一個功能單一,但非常強大的一個模塊,支持本地賬號驗證和第三方賬號登錄驗證,本文將介紹使用passport實現用戶注冊檢測,用戶登錄驗證。

passport是使用”策略“來驗證請求,策略是passport中最重要的概念。passport模塊本身不能做認證,所有的認證方法都以策略模式封裝為插件,需要某種認證時將其添加到package.json即可。

策略模式是一種設計模式,它將算法和對象分離開來,通過加載不同的算法來實現不同的行為,適用於相關類的成員相同但行為不同的場景,比如在passport中,認證所需的字段都是用戶名、郵箱、密碼等,但認證方法是不同的。

安裝

1)先創建工程

fan@dev:~/nodejs$ express -e node_passport fan@dev:~/nodejs$ cd node_passport/ fan@dev:~/nodejs/node_passport$npm install    //進入工程文件,安裝依賴

2)安裝passport

fan@dev:~/nodejs/node_passport$ npm install passport --save

3)安裝插件

你最少需要安裝一個passport策略來使用它,一般而言本地驗證策略passport-local是必裝的。

fan@dev:~/nodejs/node_passport$ npm install passport-local --save

基本使用方法

local本地驗證

1)配置Strategies

本地驗證默認是通過用戶名和密碼來驗證的,代碼如下:

assport.use(new LocalStrategy( function(username, password, done) { //操作
 }) })     

也可以通過配置,采用郵箱和密碼來驗證,本文要介紹的就是通過郵箱和密碼驗證。

代碼如下:

passport.use('local.signup',new localStategy({
        usernameField:'email',
        passwordField:'password',
        passReqToCallback:true  //此處為true,下面函數的參數才能有req
    },function(req,email,password,done){
      //....省略代碼
    })
})    

注意:

  • passport.use的第一個參數是一個字符串,是用來標識驗證方法的,因為一個工程中,可能會有多此驗證,每次驗證的邏輯會不一樣,下面會一一講解。
  • 實例化的第一個參數是一個對象,在對象里面添加你要驗證的字段,其中passReqToCallback項,默認為false,設置為true時可以將整個req傳遞給回調函數,這樣在回調里就可以驗證req中帶的所有條件。

 

在工程中實現passport的配置:

新建一個config文件下,進入該文件夾目錄下,新建passport.js文件:

fan@dev:~/nodejs/node_passport$ mkdir config fan@dev:~/nodejs/node_passport$ cd config fan@dev:~/nodejs/node_passport/config$ vim passport.js

passport.js代碼如下:

var passport = require('passport'); var User = require("../models/user.js"); var localStategy = require('passport-local').Strategy; passport.serializeUser(function(user, done) { done(null, user.id); });  passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); }); passport.use('local.signup',new localStategy({ usernameField:'email', passwordField:'password', passReqToCallback:true  //此處為true,下面函數的參數才能有req
    },function(req,email,password,done){ req.checkBody('email','您輸入的email無效').notEmpty().isEmail(); req.checkBody('password',"您輸入了無效密碼").notEmpty().isLength({min:4}); var errors = req.validationErrors(); if(errors){ var messages = []; errors.forEach(function(error){ messages.push(error.msg); }); return done(null,false,req.flash('error',messages)); } User.findOne({'email':email},function(err,user){ if(err){ return done(err); } if(user){ return done(null,false,{message:"此郵件已經被注冊"}); } var newUser = new User(); newUser.email = email; newUser.password = newUser.encryptPassword(password); newUser.save(function(err,result){ if(err){ return done(err); } return done(null,newUser); }); }); })); passport.use('local.login',new localStategy({ usernameField:'email', passwordField:'password', passReqToCallback:true  //此處為true,下面函數的參數才能有req
},function(req,email,password,done){ req.checkBody('email','您輸入的email無效').notEmpty(); req.checkBody('password',"您輸入了無效密碼").notEmpty(); var errors = req.validationErrors(); if(errors){ var messages = []; errors.forEach(function(error){ messages.push(error.msg); }); return done(null,false,req.flash('error',messages)); } User.findOne({'email':email},function(err,user){ if(err){ return done(err); } if(!user){ return done(null,false,{message:"用戶名錯誤!"}); } if(!user.validPassword(password)){ return done(null,false,{message:"密碼錯誤!"}); } return done(null,user); }); }));

上面代碼配置了用戶注冊時驗證和用戶登錄時驗證的策略,上面代碼使用到:

 var User = require("../models/user.js");    //用戶集合模型

 

newUser.encryptPassword()

 

user.validPassword()

 

 代碼如下:

var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var Schema = mongoose.Schema;

var userchema = new Schema({
    email:{type:String,required:true},
    password:{type:String,required:true}
});



userchema.methods.encryptPassword = function(password){
    return bcrypt.hashSync(password,bcrypt.genSaltSync(5),null);
};


userchema.methods.validPassword = function(password){
    return bcrypt.compareSync(password,this.password);
};


module.exports = mongoose.model('User',userchema);
View Code
 req.checkBody();      //通過var validator = require('express-validator');  引入
使用方法:
在app.js文件中添加:
var validator = require('express-validator');

app.use(validator());
View Code

2)驗證回調(會看config/passport.js代碼)

在passport.use()里面,done()有三種用法:

  • 當發生系統級異常時,返回done(err),這里是數據庫查詢出錯,一般用next(err),但這里用done(err),兩者的效果相同,都是返回error信息;
  • 當驗證不通過時,返回done(null, false, message),這里的message是可選的,可通過express-flash調用;
  • 當驗證通過時,返回done(null, user)。

3)session序列化與反序列化(會看config/passport的代碼)

 
        
passport.serializeUser(function(user, done) { done(null, user.id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); });
 
        

這里第一段代碼是將環境中的user.id序列化到session中,即sessionID,同時它將作為憑證存儲在用戶cookie中。

第二段代碼是從session反序列化,參數為用戶提交的sessionID,若存在則從數據庫中查詢user並存儲與req.user中。

4)Authenticate驗證

routes/user.js中部分代碼

router.post('/signup',
    passport.authenticate('local.signup',{
        successRedirect:'/user/profile',
        failureRedirect:'/user/signup',
        failureFlash:true
    }),function(req,res,next){

});

router.post('/login',
    passport.authenticate('local.login',{
        successRedirect:'/user/profile',
        failureRedirect:'/user/login',
        failureFlash:true
    }),function(req,res,next){

});

這里的passport.authenticate(‘local’)就是中間件,若通過就進入后面的回調函數,並且給res加上res.user,若不通過則默認返回401錯誤。

authenticate()方法有3個參數,第一是name,即驗證策略的名稱,第二個是options,包括下列屬性:

  • session:Boolean。設置是否需要session,默認為true
  • successRedirect:String。設置當驗證成功時的跳轉鏈接
  • failureRedirect:String。設置當驗證失敗時的跳轉鏈接
  • failureFlash:Boolean or String。設置為Boolean時,express-flash將調用use()里設置的message。設置為String時將直接調用這里的信息。
  • successFlash:Boolean or String。使用方法同上。

第三個參數是callback。注意如果使用了callback,那么驗證之后建立session和發出響應都應該由這個callback來做,passport中間件之后不應該再有其他中間件或callback。

5)HTTP request操作

注意上面的代碼里有個req.logIn(),它不是http模塊原生的方法,也不是express中的方法,而是passport加上的,passport擴展了HTTP request,添加了四種方法。

  • logIn(user, options, callback):用login()也可以。作用是為登錄用戶初始化session。options可設置session為false,即不初始化session,默認為true。
  • logOut():別名為logout()。作用是登出用戶,刪除該用戶session。不帶參數。
  • isAuthenticated():不帶參數。作用是測試該用戶是否存在於session中(即是否已登錄)。若存在返回true。事實上這個比登錄驗證要用的更多,畢竟session通常會保留一段時間,在此期間判斷用戶是否已登錄用這個方法就行了。
  • isUnauthenticated():不帶參數。和上面的作用相反。

完整代碼:

app.js:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var session = require('express-session');
var exphbs = require('express-handlebars');

var passport = require('passport');
var flash = require('connect-flash');

var validator = require('express-validator');
var MongoStore = require('connect-mongo')(session);


var mongoose = require('mongoose');
mongoose.connect("localhost:27017/shopping");

require('./config/passport.js');

var routes = require('./routes/index');
var userroutes = require('./routes/user');
//var users = require('./routes/users');

var app = express();

app.engine('hbs',exphbs({
    defaultLayout:'main',
    extname:'.hbs'
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

app.use(validator());

app.use(session({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: false,
    store:new MongoStore({
        mongooseConnection:mongoose.connection
    }),
    cookie:{maxAge:180*60*1000} //store保存時間
}));

//對session操作的模塊,應在session實例下面
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());

app.use(express.static(path.join(__dirname, 'public')));

app.use(function(req,res,next){
    res.locals.login = req.isAuthenticated();
    next();
});

app.use('/', routes);
app.use('/user', userroutes);
//app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});


module.exports = app;
View Code

說明:express4以后,sessions模塊被分離出來了,為了使用req.flash,我們需要安裝express-session模塊:

var session = require('express-session'); var flash = require('connect-flash');

並配置session:

app.use(session({  secret: 'keyboard cat',  resave: false, saveUninitialized: false, store:new MongoStore({ mongooseConnection:mongoose.connection }),  cookie:{maxAge:180*60*1000} //store保存時間
}));

說明:store使用來會話存儲的,需要用到connect-mongo模塊。

config/passport.js:

var passport = require('passport');

var User = require("../models/user.js");
var localStategy = require('passport-local').Strategy;

passport.serializeUser(function(user, done) {
    done(null, user.id);
});                           


passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

passport.use('local.signup',new localStategy({
        usernameField:'email',
        passwordField:'password',
        passReqToCallback:true  //此處為true,下面函數的參數才能有req
    },function(req,email,password,done){
        req.checkBody('email','您輸入的email無效').notEmpty().isEmail();
        req.checkBody('password',"您輸入了無效密碼").notEmpty().isLength({min:4});
        var errors = req.validationErrors();
        if(errors){
            var messages = [];
            errors.forEach(function(error){
                messages.push(error.msg);
            });
         return done(null,false,req.flash('error',messages));
        }
        User.findOne({'email':email},function(err,user){
            if(err){
                return done(err);
            }
            if(user){
                return done(null,false,{message:"此郵件已經被注冊"});
            }
            var newUser = new User();
            newUser.email = email;
            newUser.password = newUser.encryptPassword(password);
            newUser.save(function(err,result){
                if(err){
                    return done(err);
                }
                return done(null,newUser);
            });
        });
}));



passport.use('local.login',new localStategy({
    usernameField:'email',
    passwordField:'password',
    passReqToCallback:true  //此處為true,下面函數的參數才能有req
},function(req,email,password,done){
    req.checkBody('email','您輸入的email無效').notEmpty();
        req.checkBody('password',"您輸入了無效密碼").notEmpty();
        var errors = req.validationErrors();
        if(errors){
            var messages = [];
            errors.forEach(function(error){
                messages.push(error.msg);
            });
         return done(null,false,req.flash('error',messages));
        }
         User.findOne({'email':email},function(err,user){
            if(err){
                return done(err);
            }
            if(!user){
                return done(null,false,{message:"用戶名錯誤!"});
            }
            if(!user.validPassword(password)){
                return done(null,false,{message:"密碼錯誤!"});
            }
            return done(null,user);
        });

}));
View Code

 views/signup.ejs:

<%- include header %>

<form method="post">
    <% if(hasError){ %>
        <div class="alert alert-danger">
            <% messages.forEach(function(item){ %>
                <p> <%= item %> </p>
            <% }) %>
        </div>
    <% } %>
    郵箱: <input type="text" name="email" /><br/>
    密碼: <input type="password" name="password" /><br />
    確認密碼:<input type="password" name="password-repeat" /><br />
            <input type="submit" value="注冊" />
</form>

<%- include footer %>
View Code

測試:

在瀏覽器中輸入localhost:3000/users/signup

密碼位數少於4的情況(右圖是提交之后的顯示):

郵箱錯誤:

郵箱和密碼都錯誤:

這里只舉例用戶的注冊,用戶登錄其實也差不多,這里就不舉例了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM