从零搭建 Node.js 企业级 Web 服务器(七):认证登录

认证登录过程

认证登录就是通过第三方授权来鉴别用户信息的登录方式,比如:微信扫码登录。目前最为广泛接受的授权标准是 OAuth2.0,基于 OAuth2.0 的认证登录本质上是 Web 应用在用户授权下获取该用户在第三方应用上的个人信息的过程:

a79dc066d43e56aadeefd57c4c58f7e8a2d2e4ff.jpg

关于 passport

OAuth2.0 定义了框架但具体实现根据厂商不同多少存在差异,而 passport 模块提供了抹平这种差异的机制,通过接入不同的 strategy 对象可以对接不同的第三方登录。本章将基于上一章已完成的工程 licg9999/nodejs-server-examples - 06-session 接入 passport 模块实现 Github 认证登录。先在工程根目录安装 passport 与 passport-github

$ yarn add passport         # 本地安装 passport
# ...
info Direct dependencies
└─ passport@0.4.1
# ...

$ yarn add passport-github  # 本地安装 passport-github
# ...
info Direct dependencies
└─ passport-github@1.1.0
# ...

加上 Github 认证登录

接下来在 Github 上新建 OAuth 应用

bc617dc8a1f3311e6b96bbee7a553195a647765c.jpg

然后加上 Github 登录方式:

// src/middlewares/auth.js
const passport = require('passport');
const { Strategy: GithubStrategy } = require('passport-github');

const GITHUB_STRATEGY_OPTIONS = {
  clientID: 'b8ada004c6d682426cfb',
  clientSecret: '0b13f2ab5651f33f879a535fc2b316c6c731a041',
  callbackURL: 'http://localhost:9000/api/login/github/callback',
};

const githubStrategy = new GithubStrategy(
  GITHUB_STRATEGY_OPTIONS,
  (accessToken, refreshToken, profile, done) => {
    /**
     * 根据 profile 查找或新建 user 信息
     */
    const user = {};
    done(null, user);
  }
);

passport.use(githubStrategy);

passport.serializeUser((user, done) => {
  /**
   * 根据 user 信息获取 userId
   */
  const userId = '46e5';
  done(null, userId);
});

passport.deserializeUser((userId, done) => {
  /**
   * 根据 userId 获取 user 信息
   */
  const user = {};
  done(null, user);
});

module.exports = function authMiddleware() {
  return [passport.initialize(), passport.session()];
};

Object.assign(module.exports, { passport });
// src/middlewares/index.js
const { Router } = require('express');
const cookieParser = require('cookie-parser');
const sessionMiddleware = require('./session');
const urlnormalizeMiddleware = require('./urlnormalize');
const loginMiddleware = require('./login');
+const authMiddleware = require('./auth');

const secret = '842d918ced1888c65a650f993077c3d36b8f114d';

module.exports = async function initMiddlewares() {
  const router = Router();
  router.use(urlnormalizeMiddleware());
  router.use(cookieParser(secret));
  router.use(sessionMiddleware(secret));
  router.use(loginMiddleware());
+  router.use(authMiddleware());
  return router;
};
// src/middlewares/login.js
const { parse } = require('url');

module.exports = function loginMiddleware(
  homepagePath = '/',
  loginPath = '/login.html',
  whiteList = {
    '/500.html': ['get'],
    '/api/health': ['get'],
    '/api/login': ['post'],
+    '/api/login/github': ['get'],
+    '/api/login/github/callback': ['get'],
  }
) {
  //...
};
// src/controllers/login.js
const { Router } = require('express');
+const { passport } = require('../middlewares/auth');

class LoginController {
+  homepagePath;
+  loginPath;
+
  async init() {
    const router = Router();
    router.post('/', this.post);
+    router.get(
+      '/github',
+      passport.authenticate('github', { scope: ['read:user'] })
+    );
+    router.get(
+      '/github/callback',
+      passport.authenticate('github', {
+        failureRedirect: this.loginPath,
+      }),
+      this.getGithubCallback
+    );
    return router;
  }

  post = (req, res) => {
    req.session.logined = true;
-    res.redirect('/');
+    res.redirect(this.homepagePath);
  };
+
+  getGithubCallback = (req, res) => {
+    req.session.logined = true;
+    res.redirect(this.homepagePath);
+  };
}

-module.exports = async () => {
+module.exports = async (homepagePath = '/', loginPath = '/login.html') => {
  const c = new LoginController();
+  Object.assign(c, { homepagePath, loginPath });
  return await c.init();
};
<!-- public/login.html -->
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <form method="post" action="/api/login">
      <button type="submit">一键登录</button>
    </form>
+    <a href="/api/login/github"><button>Github 登录</button></a>
  </body>
</html>

访问 http://localhost:9000/login.html 即可体验 Github 认证登录逻辑:

25700e2c7f1ba8f99b6b1c7a58de470d075b7919.gif

需要注意由于 Github 服务器在海外,偶尔会出现网络不通导致的认证异常,此时只要稍等一段时间再试就可以了。另外,登录过的读者可以通过清除 Cookie 重置登录状态。

本章源码

licg9999/nodejs-server-examples - 07-authentication

更多阅读

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件
从零搭建 Node.js 企业级 Web 服务器(四):异常处理
从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
从零搭建 Node.js 企业级 Web 服务器(六):会话
从零搭建 Node.js 企业级 Web 服务器(七):认证登录
从零搭建 Node.js 企业级 Web 服务器(八):网络安全
从零搭建 Node.js 企业级 Web 服务器(九):配置项
从零搭建 Node.js 企业级 Web 服务器(十):日志
从零搭建 Node.js 企业级 Web 服务器(十一):定时任务
从零搭建 Node.js 企业级 Web 服务器(十二):远程调用
从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
从零搭建 Node.js 企业级 Web 服务器(十四):自动化测试
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望

认真写点好代码。

2.2k 声望
1.1k 粉丝
0 条评论
推荐阅读
再见了 Redux、Recoil、MobX、Zustand、Jotai 还有 Valtio,状态管理还可以这样做?
坚持在一线写前端代码大概有七八年了,写过一些项目,有过一些反思,越来越确信平日里一直用得心安理得某些的东西也许存在着问题,比如:在 状态管理 上一直比较流行的实践 🙏,所以试着分享出来探讨一下。

乌柏木3阅读 630

ESlint + Stylelint + VSCode自动格式化代码(2023)
安装插件 ESLint,然后 File -&gt; Preference-&gt; Settings(如果装了中文插件包应该是 文件 -&gt; 选项 -&gt; 设置),搜索 eslint,点击 Edit in setting.json

谭光志34阅读 20.8k评论 9

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城32阅读 7.3k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco24阅读 2.3k评论 3

你可能不需要JS!CSS实现一个计时器
CSS现在可不仅仅只是改一个颜色这么简单,还可以做很多交互,比如做一个功能齐全的计时器?样式上并不复杂,主要是几个交互的地方数字时钟的变化开始、暂停操作重置操作如何仅使用 CSS 来实现这样的功能呢?一起...

XboxYan25阅读 1.7k评论 1

封面图
在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 2k

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.9k评论 3

封面图

认真写点好代码。

2.2k 声望
1.1k 粉丝
宣传栏