Passport


    其中,后者我们经常使用到,如 Google, GitHub,QQ 统一登录,它们都是基于 OAuth 规范。

    是一个扩展性很强的认证中间件,支持 ,TwitterFacebook 等知名服务厂商的 Strategy,同时也支持通过账号密码的方式进行登录授权校验。

    Egg 在它之上提供了 egg-passport 插件,把初始化、鉴权成功后的回调处理等通用逻辑封装掉,使得开发者仅需调用几个 API 即可方便的使用 Passport 。

    的执行时序如下:

    • 用户访问页面
    • 检查 Session
    • 拦截跳鉴权登录页面
    • Strategy 鉴权
    • 校验和存储用户信息
    • 序列化用户信息到 Session
    • 跳转到指定页面

    下面,我们将以 GitHub 登录为例,来演示下如何使用。

    更多插件参见 GitHub Topic - egg-passport

    配置

    1. // config/plugin.js
    2. module.exports.passport = {
    3. enable: true,
    4. package: 'egg-passport',
    5. };
    6. module.exports.passportGithub = {
    7. enable: true,
    8. package: 'egg-passport-github',
    9. };

    配置:

    注意:egg-passport 标准化了配置字段,统一为 keysecret

    1. // config/default.js
    2. config.passportGithub = {
    3. key: 'your_clientID',
    4. secret: 'your_clientSecret',
    5. // callbackURL: '/passport/github/callback',
    6. // proxy: false,
    7. };

    注意:

    • 创建一个 ,得到 clientIDclientSecret 信息。
    • 填写 callbackURL,如 http://127.0.0.1:7001/passport/github/callback
      • 线上部署时需要更新为对应的域名
      • 路径为配置的 options.callbackURL,默认为 /passport/${strategy}/callback
    • 如应用部署在 Nginx/HAProxy 之后,需设置插件 proxy 选项为 true, 并检查以下配置:
      • 代理附加 HTTP 头字段:x-forwarded-protox-forwarded-host
      • 配置中 config.proxy 应设置为 true
    1. // app/router.js
    2. module.exports = app => {
    3. const { router, controller } = app;
    4. app.passport.mount('github');
    5. // 上面的 mount 是语法糖,等价于
    6. // const github = app.passport.authenticate('github', {});
    7. // router.get('/passport/github', github);
    8. // router.get('/passport/github/callback', github);
    9. }

    用户信息处理

    接着,我们还需要:

    • 首次登录时,一般需要把用户信息进行入库,并记录 Session 。
    • 二次登录时,从 OAuth 或 Session 拿到的用户信息,读取数据库拿到完整的用户信息。

    至此,我们就完成了所有的配置,完整的示例可以参见:

    egg-passport 提供了以下扩展:

    • - 获取当前已登录的用户信息
    • ctx.isAuthenticated() - 检查该请求是否已授权
    • ctx.login(user, [options]) - 为用户启动一个登录的 session
    • ctx.logout() - 退出,将用户信息从 session 中清除
    • ctx.session.returnTo= - 在跳转验证前设置,可以指定成功后的 redirect 地址
    • app.passport.verify(async (ctx, user) => {}) - 校验用户
    • app.passport.serializeUser(async (ctx, user) => {}) - 序列化用户信息后存储进 session
    • app.passport.deserializeUser(async (ctx, user) => {}) - 反序列化后取出用户信息
    • app.passport.authenticate(strategy, options) - 生成指定的鉴权中间件
      • options.successRedirect - 指定鉴权成功后的 redirect 地址
      • options.loginURL - 跳转登录地址,默认为 /passport/${strategy}
      • options.callbackURL - 授权后回调地址,默认为 /passport/${strategy}/callback
    • app.passport.mount(strategy, options) - 语法糖,方便开发者配置路由

    注意:

    • app.passport.authenticate 中,未设置 options.successRedirect 或者 options.successReturnToOrRedirect 将默认跳转 /

    的中间件很多,不可能都进行二次封装。 接下来,我们来看看如何在框架中直接使用 Passport 中间件。 以『账号密码登录方式』的 passport-local 为例:

    安装

    1. $ npm i --save passport-local
    1. // app.js
    2. const LocalStrategy = require('passport-local').Strategy;
    3. module.exports = app => {
    4. // 挂载 strategy
    5. app.passport.use(new LocalStrategy({
    6. passReqToCallback: true,
    7. }, (req, username, password, done) => {
    8. // format user
    9. const user = {
    10. provider: 'local',
    11. username,
    12. password,
    13. };
    14. debug('%s %s get user: %j', req.method, req.url, user);
    15. app.passport.doVerify(req, user, done);
    16. }));
    17. // 处理用户信息
    18. app.passport.verify(async (ctx, user) => {});
    19. app.passport.serializeUser(async (ctx, user) => {});
    20. app.passport.deserializeUser(async (ctx, user) => {});

    挂载路由

    1. // app/router.js
    2. module.exports = app => {
    3. const { router, controller } = app;
    4. router.get('/', controller.home.index);
    5. // 鉴权成功后的回调页面
    6. router.get('/authCallback', controller.home.authCallback);
    7. // 渲染登录页面,用户输入账号密码
    8. router.get('/login', controller.home.login);
    9. // 登录校验
    10. router.post('/login', app.passport.authenticate('local', { successRedirect: '/authCallback' }));
    11. };

    在上一节中,我们学会了如何在框架中使用 Passport 中间件,我们可以进一步把它封装成插件,回馈社区。

    初始化:

    package.json配置依赖:

    1. {
    2. "name": "egg-passport-local",
    3. "version": "1.0.0",
    4. "eggPlugin": {
    5. "name": "passportLocal",
    6. "dependencies": [
    7. "passport"
    8. ]
    9. },
    10. "dependencies": {
    11. "passport-local": "^1.0.0"
    12. }
    13. }

    配置:

    1. // {plugin_root}/config/config.default.js
    2. // https://github.com/jaredhanson/passport-local
    3. exports.passportLocal = {
    4. };

    注册 passport 中间件:

    1. // {plugin_root}/app.js
    2. const LocalStrategy = require('passport-local').Strategy;
    3. module.exports = app => {
    4. const config = app.config.passportLocal;
    5. config.passReqToCallback = true;
    6. app.passport.use(new LocalStrategy(config, (req, username, password, done) => {
    7. // 把 Passport 插件返回的数据进行清洗处理,返回 User 对象
    8. const user = {
    9. provider: 'local',
    10. username,
    11. password,
    12. };
    13. // 这里不处理应用层逻辑,传给 app.passport.verify 统一处理
    14. app.passport.doVerify(req, user, done);
    15. };