惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security Affairs
N
News and Events Feed by Topic
T
Tenable Blog
P
Proofpoint News Feed
W
WeLiveSecurity
Simon Willison's Weblog
Simon Willison's Weblog
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
Help Net Security
Help Net Security
I
Intezer
T
Threat Research - Cisco Blogs
S
Secure Thoughts
C
Cyber Attacks, Cyber Crime and Cyber Security
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
The Hacker News
The Hacker News
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Tor Project blog
N
News | PayPal Newsroom
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
A
Arctic Wolf
Forbes - Security
Forbes - Security
O
OpenAI News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Latest
Security Latest
P
Palo Alto Networks Blog
S
Schneier on Security
S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
H
Heimdal Security Blog
V
Vulnerabilities – Threatpost
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
T
Troy Hunt's Blog
Latest news
Latest news
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
L
LINUX DO - 热门话题
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium
V
Visual Studio Blog
H
Hacker News: Front Page

博客园 - 咖啡机(K.F.J)

Node.js躬行记(34)——用 AI 生成一个小浣熊水浒卡鉴赏网站 Node.js躬行记(33)——AI生成官网 创新聚变的2025年 2025年终活动回顾 用户体验定律和消费心理学记录 Web优化躬行记(7)——后台上传大批量图优化 Node.js躬行记(31)——IP白名单变迁史 带团队后的日常思考(十七) Node.js躬行记(30)——SkyWalking使用和排查分析 童年小浣熊水浒卡探究(二) 童年小浣熊水浒卡探究(一) 推陈出新的2024年 带团队后的日常思考(十六) 设计心理学 设计能力量化 《简约至上 交互设计四策略》记录 前端体验优化(5)——后台 带团队后的日常思考(十五) 两个有趣的AI项目 研发效能与稳定性保障 可观测性 带团队后的日常思考(十四)
Node.js躬行记(32)——F2A实战
咖啡机(K.F.J) · 2026-04-30 · via 博客园 - 咖啡机(K.F.J)

  近期管理后台有不明人士在登录,查询后台日志只能查到一个IP,其他日志因为时间的原因,已经被清除。后台其实已经做了很多防护,可以避免不法分子攻击,例如账号密码加密传输、频繁登录会被封禁、密码错误3次也会被封禁等。但为了能让后台更加安全,遂试着加上了F2A校验。

  F2A(Two-Factor Authentication)其实是2FA的笔误,都表示双因素认证。它是一种安全验证机制,要求用户在登录时提供两种不同类型的证据(因素)来证明身份。

  在实际应用中,F2A/2FA 最常见的表现形式是:

  1. 密码 + 短信验证码:最普遍但安全性相对较低(易受 SIM 卡交换攻击)。
  2. 密码 + 基于时间的一次性密码( the Time-Based One-time Password ,TOTP):如 Google Authenticator、Microsoft Authenticator 等应用生成的 6-8 位动态数字。
  3. 密码 + 硬件密钥:如 YubiKey,安全性最高,能有效防御网络钓鱼。

  我们的后台将会采用第二种表现形式,后台服务端基于 Node.js 实现。核心实现原理是基于时间同步的一次性密码(TOTP,Time-based One-Time Password)算法,通过“共享密钥”和“时间”两个要素,在用户设备与服务器之间生成相同的动态验证码。即服务器和你的手机APP,各自使用同一个密钥和当前时间,通过相同的算法计算,得出一串相同的数字。

一、生成密钥

1)speakeasy.js

  Node的生态提供了speakeasy.js库,可以生成F2A的密钥,还有供F2A设备扫描的地址。

npm install --save speakeasy

  在项目中安装完成后,就是调用 speakeasy 的生成方法。

  router.get(
    '/get/f2a',
    async (ctx) => {
      const secret = speakeasy.generateSecret({ length: 20 });
      ctx.body = {
        code: 0,
        data: {
          secret: secret.base32,
          url: secret.otpauth_url,
        }
      };
    },
  );

  secret 就是一串字符,而 url 是个 otpauth 协议的地址,生成二维码后便于设备扫描。

{
  "secret": "M4STOUBFGM4UUXJXKZJXS5J4IIWEA3KU",
  "url": "otpauth://totp/SecretKey?secret=M4STOUBFGM4UUXJXKZJXS5J4IIWEA3KU"
}

  node-169

2)账户绑定密钥

  点击上图中的重新绑定按钮,就能将当前 secret 和后台账户绑定起来,在账户表中新增两个字段:otpauthUrl 和 secret。

{
  "_id": {
    "$oid": "5f81288d3578bb005a79cdc1"
  },
  "status": 1,
  "realName": "测试",
  "userName": "xx@xx.me",
  "cellphone": "13800138000",
  "password": "abcd",
  "otpauthUrl": "otpauth://totp/SecretKey?secret=PU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ",
  "secret": "PU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ"
}

  后续在登录输入安全码后,就可进行校验了。

二、绑定应用

1)APP

  在应用市场提供了很多的APP,用于显示安全码,界面基本上都是下图这样,例如 authy2FAS 等。

  node-170

2)小程序

  有个叫二次验证码的小程序,也能用于绑定。

  node-171

3)飞书小助手

  还设计了一种更简便的查询方式。

  在机器人的对话框,输入安全码,空格后面跟上自己管理后台的账号邮箱,例如“安全码 xx@xx.com”。

  如此,就能得到该账户的最新安全码。

  node-172

  原理就是查表,然后调用 speakeasy 的 totp() 方法,再调用飞书的接口发送消息。

const account = await this.models.BackendUserAccount.findOne({ userName: email });
const token = speakeasy.totp({
  secret: account.secret,
  encoding: 'base32',
});
// 发送回复消息
await this.messageService.sendTextMessage(chat_id, token);

  不过还有个小问题,就是也能收到别人的安全码。但后台都有查询记录,若出现问题,还能溯源。

1)开关

  增加一步登录校验,在使用时会增加门槛,所以在通用配置中设计了一个开关。

{
  "isOpen": false,
  "whiteList": [
    "yy@yy.com"
  ]
}

  只有在 isOpen 为 true 时,才会开启校验。

  whiteList 是给特殊账户开启的白名单,不需要走校验,例如给第三方用的账户。

2)登录

  登录设计成了两步,两次调用的接口都是 user/login。

  第一步还是原先的输入邮箱和密码。

  输入完后,去请求登录接口,判断是否需要二次校验,若返回JSON包含 f2a。

  则显示弹框,安全码为必填项,点击确定进行二次校验。

  node-173

  下面是登录接口中的部分逻辑,从通用配置中读取开关信息,判断是否弹框,最后校验。

// 判断是否需要F2A安全校验
const configContent = await services.tool.getConfigContent({ 
  key: '1a26f4185f66d6f94ef3897f7a475305' 
});
if(configContent && 
  configContent.isOpen && 
  configContent.whiteList.indexOf(userName) === -1) {
  // 未传随机码,说明是第一步校验,直接返回
  if(!code) {
    ctx.body = { f2a: true };
    return;
  }
  // 校验随机码
  const verifyInfo = {
    secret: account.secret,
    encoding: 'base32',
    token: code,
  }
  const verify = speakeasy.totp.verify(verifyInfo);
  if(!verify) {
    ctx.status = 400;
    ctx.body = { error: '安全码错误' };
    return;
  }
}

3)过渡期

  在开启这个功能前,会有一段时间的过渡期,在登录页面增加说明文档。

  完善密钥,过渡期后,才会正式开启此功能。

  node-174