zoukankan      html  css  js  c++  java
  • 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的情况(右图是提交之后的显示):

    邮箱错误:

    邮箱和密码都错误:

    这里只举例用户的注册,用户登录其实也差不多,这里就不举例了。

  • 相关阅读:
    2018第一发:记一次【Advanced Installer】打包之旅
    Nginx 实现端口转发
    php支付宝手机网页支付类实例
    磁盘阵列操作实战
    错误修改/etc/fstab,导致系统无法开机
    linux 查看机器的cpu,操作系统等命令
    shell实现https登录
    linux tomcat配置https
    ArrayList和Vector以及synchronizedList
    java synchronized修饰普通方法,修饰静态方法,修饰代码块,修饰线程run方法 比较
  • 原文地址:https://www.cnblogs.com/y-yxh/p/5859937.html
Copyright © 2011-2022 走看看