uniquejava / blog

My notes regarding the vibrating frontend :boom and the plain old java :rofl.
Creative Commons Zero v1.0 Universal
11 stars 5 forks source link

passportjs #126

Open uniquejava opened 7 years ago

uniquejava commented 7 years ago

这里是最详尽的文档: http://passportjs.org/docs/authenticate 上面的文档对flash message的叙述有误 见Node.js Authentification with Passport: How to flash a message if a field is missing?

其中有提到这篇博客: https://scotch.io/tutorials/easy-node-authentication-setup-and-local 写得非常详细.

Quick start

要快速开始可以看github page首页上的express 4 demo, 按我的理解快速记下几个要点:

passport只用在login的那一刻, 也就是说, 有且只用在一个地方.

router.post('/login', passport.authenticate('strategyName', {params}), (req, res) => {
  //这里就可以使用req.user得到用户, 会包含在 deserializeUser 函数中传入的 user 数据
}

在其它地方可以使用req.isAuthenticated()来判断用户是否登录了, 但我们一般都会把user放在session里, 然后判断req.session.user是否存在, 所以这个isAuthenticated方法不是很有必要.

要使用上面的代码能正常运行, 需要向passport注册一些Strategy(目前有300多种strategy可用), 官网首页上有个列表, qq, weibo, weixin, twitter, facebook你能想到的都在里边.

这个strategy name默认叫'local', 就是常用的用form表单提交用户密码的情况. 以下是配置部分, 用代码说话:

Strategies 注册Strategy

passport.use(/* 'your random strategy name here' */, new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      if (!user.verifyPassword(password)) { return done(null, false); }
      return done(null, user);
    });
  }

Sessions 序列化与反序列化

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

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

Middleware 依赖的组件

var app = express();
app.use(require('serve-static')(__dirname + '/../../public'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());

Authenticate Requests 仅用在登录处

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  });

也可以这样

router.post('/', passport.authenticate('local', { successRedirect: 'http://localhost:3000', failureRedirect: '/login' }));

References

http://passportjs.org/

https://github.com/jaredhanson/passport

使用passportjs进行登录验证

使用 passport.js 完成后台验证

uniquejava commented 7 years ago

关于flash message

cyper实战一, 定义 passport.local.js:

var passport = require('passport');
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy({
    usernameField: 'username',
    passwordField: 'password',
    passReqToCallback: true
  },
  function (req, username, password, done) {

    fs.readFile(path.join(__dirname, './data/users.json'), 'utf8', function (err, doc) {
      if (err) {
        return done(err);
      }
      var doc = JSON.parse(doc);

      var user = _.findWhere(doc, {username: username});
      if (!user) {
        req.flash("field", "username");
        req.flash("message", "Incorrect username.");
        return done(null, false);
      }

      if (user.password !== password) {
        req.flash("field", "password");
        req.flash("message", "Incorrect password.");
        return done(null, false);
      }

      return done(null, username);

    });
  }
));

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

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

module.exports = passport;

使用indexRoutes.js

router.get('/login', function (req, res, next) {

  var fields = req.flash('field');
  var messages = req.flash('message');

  res.render('login', {field: fields[0], message: messages[0]});
});

router.post('/login', passport.authenticate('local', {
  failureRedirect: '/login',
  failureFlash: true /* <=== 这里改成false也不影响. */
}), function (req, res) {
  var user = req.user;

  console.log(user + ' logged in.');

  req.session.user = user;
  res.cookie('token', someToken);
  res.redirect(user === 'admin' ? '/admin' : '/xxxx');

});

页面 login.ejs

function login() {
  var rules = {
    username: {
      identifier: "username",
      rules: [{type: 'empty'}]
    },
    password: {
      identifier: "password",
      rules: [{type: 'empty'}]
    },
  };

  var form = $('.login-form').form({fields: rules});
  form.form("validate form");
  var isValid = form.form('is valid');
  if (isValid) {
    createCookie('username', $('#username').val());
    createCookie('remember', $('.ui.checkbox').checkbox('is checked'));

    $(this).prop("disabled", true).toggleClass("loading", true);
    form.form('submit');
  }
}

$(document).ready(function () {
  // remember me
  var remember = getCookie('remember');
  if (remember === 'false') {
    $('#username').val('');
    $('.ui.checkbox').checkbox('uncheck');

  } else {
    $('#username').val(getCookie('username'));
    $('.ui.checkbox').checkbox('check');
  }

  // display error message if exist
  var field = '<%= field %>';
  var message = '<%= message %>';
  if (field) {
    $('#username').val(getCookie('username'));

    var form = $('.login-form').form({inline: true});
    form.form('add prompt', field, message);
  }

  // handle text input key up event
  $('#username').keyup(function (event) {
    if (event.keyCode === 13) {
      $('#password').focus();
    }
  });

  $('#password').keyup(function (event) {
    if (event.keyCode === 13) {
      login();
    }
  });

  $('#btnSubmit').click(login);

});

登出 xxxRoutes.js


router.get('/logout', function (req, res, next) {
  // see https://stackoverflow.com/questions/33332614/either-req-logout-or-req-session-destroy-does-not-work
  // see https://github.com/jaredhanson/passport/issues/246

  req.session.destroy(function () {
    req.logout();

    console.log("user session destroyed.");
    console.log(req.isAuthenticated());

    res.redirect('/login');
  });
});

为什么要调用logout, 解释如下

req.isAuthenticated() is part of passport. Relevant code:

req.isAuthenticated = function() {
  var property = 'user';
  if (this._passport && this._passport.instance._userProperty) {
    property = this._passport.instance._userProperty;
  }

  return (this[property]) ? true : false;
};

Checks for the property and returns a boolean.

req.logout() removes the property so it returns false in future requests.

Meanwhile, session.destroy comes from expressjs/session middleware, so it's not passport related. Maybe you are creating the session again in the index page. The question needs more info.

uniquejava commented 7 years ago

passport.js关键方法的调用时机

通过以下日志可以看到, 每一次request请求, 都会首先调用passport.deserializeUser(..), passport从用户请求的cookie 中得到sessionid进而得到保存在session中的user对象, 然后调用deserializeUser(得到完整的user对象). 然后将user放到req.user中, 接下来进入到mustlogin middleware, 由这个middleware决定下一步怎么走

而LocalStrategy的回调和serializeUser只会在POST /login时调用一次.

[2017-09-28 01:05:58.782 INFO] GET /login
[2017-09-28 01:06:02.934 INFO] passport.use(new LocalStrategy(...))
[2017-09-28 01:06:02.937 INFO] get one connection
[2017-09-28 01:06:02.937 DEBUG] select * from XXXX where username=? order by user_id desc
[2017-09-28 01:06:02.937 DEBUG] [ 'admin' ]
[2017-09-28 01:06:03.547 INFO] connection released to pool.
[2017-09-28 01:06:03.552 INFO] serializeUser(...) {"user_id":1,"username":"admin","org_id":1}
[2017-09-28 01:06:03.553 INFO] {"user_id":1,"username":"admin","org_id":1} logged in.
[2017-09-28 01:06:03.763 INFO] POST /login
[2017-09-28 01:06:03.768 INFO] deserializeUser(...) {"user_id":1,"username":"admin","org_id":1}
[2017-09-28 01:06:03.769 INFO] mustlogin - route middleware: req.isAuthenticated()?
[2017-09-28 01:06:03.993 INFO] GET /admin
[2017-09-28 01:06:06.082 INFO] deserializeUser(...) {"user_id":1,"username":"admin","org_id":1}
[2017-09-28 01:06:06.083 INFO] mustlogin - route middleware: req.isAuthenticated()?
[2017-09-28 01:06:06.758 INFO] GET /admin/answers/index
[2017-09-28 01:06:07.336 INFO] deserializeUser(...) {"user_id":1,"username":"admin","org_id":1}
[2017-09-28 01:06:07.336 INFO] mustlogin - route middleware: req.isAuthenticated()?
[2017-09-28 01:06:07.337 INFO] get one connection
[2017-09-28 01:06:07.338 DEBUG] select ID,substring(text, 0, 35) || '...' as title from XXXX
[2017-09-28 01:06:07.338 DEBUG] []
[2017-09-28 01:06:07.949 INFO] connection released to pool.
[2017-09-28 01:06:08.207 INFO] GET /admin/answers