Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

passportjs #126

Open
uniquejava opened this issue Aug 30, 2017 · 2 comments
Open

passportjs #126

uniquejava opened this issue Aug 30, 2017 · 2 comments

Comments

@uniquejava
Copy link
Owner

uniquejava commented Aug 30, 2017

这里是最详尽的文档: 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
Copy link
Owner Author

uniquejava commented Sep 16, 2017

关于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
Copy link
Owner Author

uniquejava commented Sep 27, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant