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

[RFC] 抽取 passport 模型 #38

Closed
2 of 5 tasks
fengmk2 opened this issue Aug 9, 2016 · 34 comments
Closed
2 of 5 tasks

[RFC] 抽取 passport 模型 #38

fengmk2 opened this issue Aug 9, 2016 · 34 comments

Comments

@fengmk2
Copy link
Member

fengmk2 commented Aug 9, 2016

  • egg-passport-github
  • egg-passport-weibo @shaoshuai0102
  • egg-passport-gitlab
  • egg-passport-google
  • egg-passport-taobao

RFC: passport

由于业界的 passport 已经足够简单,egg 做起来就是配置。对应应用开发者,更加是配置,基本无需编写代码就能实现。

一个应用的 authenticate 通用过程

  1. 用户会选择应用提供的其中一种 strategy 进行 authenticate 登录,如应用同时提供了 twitter 和 facebook 两种方式。
  2. 用户去 twitter 登录成功后,会返回应用网站,然后 egg-passport-twitter 插件,会使用 verify callback 来完成对统一约定 user 的封装,继续返回给 egg-passport 做后续处理。
  3. egg-passport 会触发 app.passport.verify hook,这样应用层就能做最终的核实,例如做新用户注册,老用户绑定校验等等,这里也是用户数据持久化的地方。
  4. 用户登录之后,每次访问应用都会根据 app.passport.deserializeUserapp.passport.serializeUser 来还原 session。

说明

  • 框架内置了高度抽象的 passport 插件。
  • passport-xxx 插件都需要声明依赖内置的 passport 插件。
  • passport-xxx 插件开发者只需要简单结合一下 http://passportjs.org 现有的模块,就可以快速完成一个网站的 authenticate 接入。
    如要对接 Twitter 的 oauth,那么只需要引入 passport-twitter,并实现一个 passport-twitter 插件即可。

统一 user 字段约定

ctx.user 是一个 getter,真实数据来自于 ctx.state.user

每个 passport-xxx 插件都需要按照约定封装一个 user。

  • 必须 user.provider: 用户信息来自那种登录策略,如 twitter, weibo, facebook,这样就能通过此字段读取 user.profile 信息了。一般都是自动填充
  • 必须 user.id: 用户 id,字符串
  • 必须 user.name: 用户名
  • 可选 user.displayName: 昵称
  • 可选 user.token: oauth1 的话必须提供
  • 可选 user.tokenSecret: oauth1 的话必须提供
  • 可选 user.accessToken: oauth2 的话必须提供
  • 可选 user.refreshToken: oauth2 的话必须提供
  • 可选 user.profile: profile 的其他字段,每个平台提供的都不太一样,需要各种区别

passport 配置

// config/config.${env}.js
exports.passport = {
  twitter: {
    consumerKey: 'your-consumer-key',
    consumerSecret: 'your-consumer-secret',
    // 更多配置请查看 passport-twitter 插件
  },
};

应用需要关注的3个用户数据处理 hooks

// app.js
module.exports = app => {
  // 1. 核实登录用户信息,自行判断是否需要做数据调整,
  // 也可以在这里由应用自身统一 user 的数据结构,并且保存到数据库。
  // - 必须返回核实后的 user,即 verifiedUser,数据结构可以由应用自行统一约定
  // - 核实失败,throw 一个带有 status = 401 的 error 异常
  // - 其他异常,将会当作服务端内部异常处理
  //
  // passport-xxx 插件已经按约定生成了 user 数据,应用可以在此做最终的用户校验,并且做数据持久化
  // 在 strategy.authenticate 成功之后调用
  app.passport.verify(function* (ctx, user) {
    
  });
  
  // 自行处理经过 verify 后的 verifiedUser 数据如何序列化到 session
  // 在 http 请求结束阶段,保存数据到 session 之前调用
  app.passport.serializeUser(function* (ctx, verifiedUser) {
    // you can store profile and handle token here
  });
  
  // 根据 sesssion 中的 sessionUser 信息,还原出 verifiedUser
  // 在 http 请求开始阶段,还原 session 之后调用
  app.passport.deserializeUser(function* (ctx, sessionUser) {

  });
};
@fengmk2 fengmk2 added this to the v1.x milestone Aug 9, 2016
@fengmk2
Copy link
Member Author

fengmk2 commented Aug 9, 2016

以 oauth 登录作为标准的示例实现。

@fengmk2
Copy link
Member Author

fengmk2 commented Aug 9, 2016

http://passportjs.org/

@popomore
Copy link
Member

popomore commented Aug 9, 2016

哈哈,这个也挺老了

@fengmk2
Copy link
Member Author

fengmk2 commented Aug 9, 2016

@popomore 我看它是最完善的。

@fengmk2
Copy link
Member Author

fengmk2 commented Aug 9, 2016

各种插件一直都在更新 http://web.npm.alibaba-inc.com/package/passport-outlook

@popomore popomore modified the milestone: v1.x Aug 13, 2016
@fengmk2
Copy link
Member Author

fengmk2 commented Aug 16, 2016

@popomore popomore removed this from the v1.x milestone Oct 24, 2016
@fengmk2 fengmk2 changed the title userservice demos, use the open oauth api 抽取 passport 模型 Jan 12, 2017
@popomore popomore modified the milestone: 1.x Jan 13, 2017
@fengmk2
Copy link
Member Author

fengmk2 commented Feb 14, 2017

淘宝登录也有需求 egg-taobao-login

@atian25
Copy link
Member

atian25 commented Feb 14, 2017

egg-dingtalk-login 🤔

@fengmk2 fengmk2 self-assigned this Feb 14, 2017
@shaoshuai0102
Copy link
Contributor

认领 egg-weibo-login,接一个先试试

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 14, 2017

看来还是得封装 egg-passport-xxx,因为需要统一约定处理 afterAuth,并且将 {token, tokenSecret, profile} 转换为统一字段约定的 user,至少要包含哪些统一命名的字段。

@atian25
Copy link
Member

atian25 commented Feb 14, 2017

这些 passport 的 adapter 跟插件有些区别吧,需允许多个共存,不一样的 eggPlugin.name?

@popomore
Copy link
Member

感觉直接传 Strategy 实例比较好,不同的 Strategy 参数不一样,也可能会有两个。

exports.passport = {
  strategy: [
    new TwitterStrategy({
      consumerKey: TWITTER_CONSUMER_KEY,
      consumerSecret: TWITTER_CONSUMER_SECRET,
      callbackURL: "http://127.0.0.1:3000/auth/twitter/callback"
    },
    function(token, tokenSecret, profile, cb) {
      User.findOrCreate({ twitterId: profile.id }, function (err, user) {
        return cb(err, user);
      });
    })
  ]
};

@popomore
Copy link
Member

把 passport 的 callback API 都要改成 promise 的

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 15, 2017

auto0 还有一个 params 参数: accessToken, refreshToken, extraParams, profile https://github.com/auth0/passport-auth0#configuration

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 15, 2017

@popomore 还是改成 passport-xx 模式了,每个插件都需要按照统一 user 数据格式实现 verify 方法,生成一个 user,再由应用自行实现总的 verify 来做最终用户校验。没办法每个插件能使用到数据库实现 verify 的。

@popomore
Copy link
Member

但这个就不算 passport 了,用什么来存储用户信息和 passport 没关系。内部可以直接在 Strategy 里面封装获取用户信息。

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 15, 2017

内部可以直接在 Strategy 里面封装获取用户信息。

@popomore 对啊,内部应用代码就不需要实现 verify 了

@popomore
Copy link
Member

app.passport.verify 的 hook 就是替换 strategy 里的 verify callback 么?

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 16, 2017

@popomore 不完全是,strategy verify callback 有 passport-xxx 插件实现。而 app.passport.verify 是我们封装过的,给应用使用的。这样一个应用同时接入多种 strategy,都可以通过同一个 app.passport.verify 实现最终核实过程。

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 16, 2017

demo eggjs/examples#14

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 16, 2017

对于每次都去缓存读取用户信息的 user,需要默认关闭 passport-session 策略。

const auth = app.passport.authenticate('alipaysession', { 
  session: false, 
  successReturnToOrRedirect: false,
  successRedirect: false,
});

// 将 auth 加入全局中间件,让每个请求都过 alipaysession
app.use(auth);

但是要追求高性能的话,alipaysession 应该直接走普通中间件,不走 passport 这台,因为它是每次请求都去缓存读取数据的。

@fengmk2
Copy link
Member Author

fengmk2 commented Feb 16, 2017

@shaoshuai0102 https://github.com/eggjs/egg-passport-weibo 基本不需要做,我需要想想怎么 mock 方便写单元测试才是王道。

fengmk2 added a commit to eggjs/egg-passport that referenced this issue Feb 21, 2017
@atian25
Copy link
Member

atian25 commented Feb 27, 2017

这个是不是还缺篇文档?

@popomore
Copy link
Member

恩,要加一下

@atian25 atian25 reopened this Feb 27, 2017
@yandongxu
Copy link
Contributor

passport-local 该怎么做?有参考么?

@dangyanglim
Copy link

@yandongxu 我也不会这个 passport-local

@leiyanggz
Copy link

@yandongxu @dangyanglim
https://github.com/leiyanggz/egg-passport-local

@popomore
Copy link
Member

popomore commented May 9, 2017

@leiyanggz 这个没写测试?

@jackhutu
Copy link

为什么在egg-passport中, 重写passport的方法时, 要把callback去掉, 是暂时不支持还是不打算支持呀?

@popomore
Copy link
Member

@jackhutu 我们最佳实践是 generator function 和 async function,所有的代码应该看不到 callback。

@zsbox
Copy link

zsbox commented Mar 7, 2018

async login() {
  const { body } = this.ctx.request;
  const { email: _email, password: _password } = body;
  const rule = {
    email: {
      type: 'email',
    },
    password: {
      type: 'password',
    },
  };
  try {
     this.ctx.validate(rule);
  } catch (err) {
    console.log(err);
    this.ctx.redirect('/user');
    return;
  }
  const user = await this.app.mysql.get('users', { email: _email });
  const dbpw = user.password;
  const fmpw = crypto.MD5(_password).toString();
  if (user && dbpw === fmpw) {
     console.log('登陆成功');
     this.app.passport.serializeUser(async (ctx, user) => {
       console.log('000000', user);
       // 保存用户信息到session
     });
    this.ctx.redirect('/user');
  } else {
    console.log('登陆失败');
  }
}

@fengmk2 本地鉴权怎么用啊?感觉调用this.app.passport.serializeUser并没有成功啊 session里没有东西
this.app.passport.deserializeUser方法也没用 教程省略了太多 对小白太难了 求指点

@atian25
Copy link
Member

atian25 commented Mar 7, 2018

app.passport.serializeUser 是写在 app.js 的

elrrrrrrr pushed a commit to elrrrrrrr/egg that referenced this issue Feb 21, 2023
tsc throw errors when tsconfig.json set "declaration": true

config/defaultConfig.ts(9,1): error TS4082: Default export of the module has or is using private name 'DefaultConfig'.
config/defaultConfig.ts(13,28): error TS4033: Property 'config' of exported interface has or is using private name 'DefaultConfig'.

define interface NewsItem as return type of HackerNews.getItem() for types recognition

app/controller/news.ts: variable newsList can be recognized as type of NewsItem[] correctly instead of an object missing array
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants