学习

使用 Redis 的手机银行身份验证和会话存储

Will Johnston
作者
Will Johnston, Redis 开发者增长经理
Prasan Kumar
作者
Prasan Kumar, Redis 技术解决方案开发者
GITHUB 代码

以下是克隆本教程中使用的应用源代码的命令

git clone --branch v1.2.0 https://github.com/redis-developer/mobile-banking-solutions

手机银行的身份验证和会话存储是什么?#

用户成功输入登录凭据后,手机银行应用会使用服务器创建的 token 和 sessionId 来代表用户身份。该 token 在 Redis 中存储用户会话期间,并在登录响应中发送给银行应用客户端(手机/浏览器)。然后客户端应用在每个请求中发送该 token 到服务器,服务器在处理请求之前对其进行验证。

注意

Redis Stack 支持 JSON 数据类型,允许您对 JSON 和 更多 内容进行索引和查询。因此您的会话存储不局限于简单的键值字符串化数据。

会话存储在用户导航应用的整个会话期间保存与每个用户相关的关键信息。手机银行会话数据可能包括但不限于以下信息:

  • 用户个人资料信息,例如姓名、出生日期、电子邮件地址等。
  • 用户权限,例如 user、 admin、 supervisor、 super-admin 等。
  • 其他与应用相关的数据,例如最近交易、余额等。
  • 会话过期时间,例如从现在起一小时、从现在起一周等。

为什么应该使用 Redis 进行手机银行会话管理?#

  • 弹性:Redis Cloud 提供令人难以置信的弹性,可达到 99.999% 的正常运行时间。毕竟,身份验证令牌存储必须提供全天候可用性。这确保用户可以不间断地 24/7 访问其应用。
  • 可伸缩性:令牌存储需要具有高度可伸缩性,以便在高 用户量 同时进行身份验证时不会成为瓶颈。Redis Cloud 提供 小于 1 毫秒的延迟,同时吞吐量极高(高达 每秒 1 亿次操作),这使得身份验证和会话数据访问更快!
  • 与常用库和平台的集成:由于 Redis 开源版已集成到大多数会话管理库和平台中,因此从开源 Redis 升级时,Redis Cloud 可以无缝集成(例如,本教程演示了 express-session 和 connect-redis-stack 库的集成)。
提示

阅读我们的电子书,它回答了这个问题: JSON Web Token (JWT) 安全吗? 它讨论了何时以及如何安全地使用 JWT,并提供了久经验证的会话管理解决方案。

使用 Redis 构建会话管理#

GITHUB 代码

以下是克隆本教程中使用的应用源代码的命令

git clone --branch v1.2.0 https://github.com/redis-developer/mobile-banking-solutions

下载以上源代码并运行以下命令以启动演示应用

docker compose up

Docker 启动并运行后,在浏览器中打开 http://localhost:8080/ URL 查看应用

数据填充#

此应用利用了 Redis 核心数据结构、JSON、TimeSeries、搜索和查询功能。填充的数据随后用于展示可搜索的交易概览(包含实时更新)以及个人财务管理概览(包含实时余额和最大支出者更新)。

在 app/server.js 的应用启动时,计划了一个 cron 任务,用于定期创建随机银行交易并将其填充到 Redis 中。

app/server.js
//cron job to trigger createBankTransaction() at regular intervals

cron.schedule('*/10 * * * * *', async () => {
  const userName = process.env.REDIS_USERNAME;

  createBankTransaction(userName);

  //...
});
  • 交易生成器会创建随机的银行借记或贷记,这将反映在(默认)用户起始余额 $100,000.00 上
  • 交易数据 在 Redis 中保存为 JSON 文档。
  • 为了捕获 随时间变化的余额,每笔交易的 balanceAfter 值记录在键为 balance_ts 的 TimeSeries 中。
  • 为了追踪 最大支出者,有序集合 bigspenders 中关联的 fromAccountName 成员会按交易金额递增。请注意,该金额可以是正数或负数。
app/transactions/transactionsGenerator.js
let balance = 100000.0;
const BALANCE_TS = 'balance_ts';
const SORTED_SET_KEY = 'bigspenders';

export const createBankTransaction = async () => {
  //to create random bank transaction
  let vendorsList = source.source; //app/transactions/transaction_sources.js
  const random = Math.floor(Math.random() * 9999999999);

  const vendor = vendorsList[random % vendorsList.length]; //random vendor from the list

  const amount = createTransactionAmount(vendor.fromAccountName, random);
  const transaction = {
    id: random * random,
    fromAccount: Math.floor((random / 2) * 3).toString(),
    fromAccountName: vendor.fromAccountName,
    toAccount: '1580783161',
    toAccountName: 'bob',
    amount: amount,
    description: vendor.description,
    transactionDate: new Date(),
    transactionType: vendor.type,
    balanceAfter: balance,
  };

  //redis json feature
  const bankTransaction = await bankTransactionRepository.save(transaction);
  console.log('Created bankTransaction!');
  // ...
};

const createTransactionAmount = (vendor, random) => {
  let amount = createAmount(); //random amount
  balance += amount;
  balance = parseFloat(balance.toFixed(2));

  //redis time series feature
  redis.ts.add(BALANCE_TS, '*', balance, { DUPLICATE_POLICY: 'first' });
  //redis sorted set as secondary index
  redis.zIncrBy(SORTED_SET_KEY, amount * -1, vendor);

  return amount;
};

使用 RedisInsight 查看示例 bankTransaction 数据

提示

下载 RedisInsight 以查看您的 Redis 数据或在工作台中试用原始 Redis 命令。

会话配置#

Redis 已集成到许多会话管理库中,我们将在此演示中使用 connect-redis-stack 库,它为您的 express-session 应用提供 Redis 会话存储。

以下代码演示了如何配置 Redis 会话以及如何与 express-session 集成。

app/server.js
import session from 'express-session';
import { RedisStackStore } from 'connect-redis-stack';

/* configure your session store */
const store = new RedisStackStore({
  client: redis, //redis client
  prefix: 'redisBank:', //redis key prefix
  ttlInSeconds: 3600, //session expiry time
});

const app = express();

// ...

app.use(
  session({
    store: store, //using redis store for session
    resave: false,
    saveUninitialized: false,
    secret: '5UP3r 53Cr37', //from env file
  }),
);

//...
app.listen(8080, () => console.log('Listening on port 8080'));

登录 API(会话 ID 生成)#

让我们看一下 /perform_login API 代码,该代码在 登录页面 点击登录按钮时触发。

由于 connect-redis-stack 是 Express 中间件,会话在请求开始时自动创建,并在 HTTP(API) 响应结束时更新,如果 req.session 变量被修改。

app.post('/perform_login', (req, res) => {
  let session = req.session;
  console.log(session);
  /*
  Session {
    cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
  }
  */
  //hardcoded user for demo
  if (req.body.username == 'bob' && req.body.password == 'foobared') {
    //on successful login (for bob user)
    session = req.session;
    session.userid = req.body.username; //create session data
    res.redirect('/index.html');
  } else {
    res.redirect('/auth-login.html');
  }
});

在上述代码中,登录成功后(针对“bob”用户), session.userid 变量被赋值,因此在 Redis 中创建了一个包含分配数据的会话,客户端 cookie 中仅存储了 Redis 键(sessionId)。

  • 成功登录后的仪表盘页面
  • Redis 中的会话条目
  • 在仪表盘页面打开开发者工具检查客户端 cookie connect.sid (仅包含 sessionId)

现在,客户端的每个其他 API 请求, connect-redis-stack 库都会根据客户端 cookie (sessionId) 从 Redis 加载会话详情到 req.session 变量。

余额 API(会话存储)#

参考以下 /transaction/balance API 代码以演示会话存储。

我们需要修改 req.session 变量以更新会话数据。我们添加更多会话数据,例如用户的当前余额。

app/routers/transaction-router.js
/* fetch all transactions up to an hour ago /transaction/balance */
transactionRouter.get('/balance', async (req, res) => {
  const balance = await redis.ts.range(
    BALANCE_TS,
    Date.now() - 1000 * 60 * 5,
    Date.now(),
  );

  let balancePayload = balance.map((entry) => {
    return {
      x: entry.timestamp,
      y: entry.value,
    };
  });

  let session = req.session;
  if (session.userid && balancePayload.length) {
    //adding latest BalanceAmount to session
    session.currentBalanceAmount = balancePayload[balancePayload.length - 1]; //updating session data
  }

  res.send(balancePayload);
});
  • Redis 中更新的会话条目,包含 currentBalanceAmount 字段('x' 表示时间戳,'y' 表示该时间戳下的余额)
  • 在仪表盘 UI 中验证最新余额

准备好在会话管理中使用 Redis 了吗?#

希望本教程能帮助您了解如何在会话管理中更好地使用 Redis,尤其是在手机银行场景下。有关此主题的其他资源,请查看以下链接:

其他资源#