以下是如何克隆本教程中使用的应用程序源代码的命令
git clone --branch v3.0.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions
随着数字环境的不断发展,对强大的安全措施的需求变得越来越重要,以保护用户和组织。数字身份验证和欺诈检测是任何全面安全计划中不可或缺的组成部分。本文将探讨数字身份验证的重要性、该领域面临的挑战以及使用 redis 进行数字身份验证的解决方案。
"了解您的客户" (KYC) 规定是指金融机构和其他受监管企业必须遵循的一套政策和程序,以验证其客户的身份。客户详细信息可以是姓名、地址、出生日期和其他政府签发的身份证明文件。
作为 KYC 的一部分,企业必须评估每个客户带来的潜在风险,并对他们的交易和行为进行 持续监控 以检测任何可疑活动。KYC 规定由监管机构执行,不遵守规定会导致经济处罚和声誉受损。
KYC 规定旨在防止洗钱、恐怖主义融资和其他非法活动。金融服务公司正在通过减少对静态身份验证方法(基于知识的身份验证或 KBA)的依赖,转而转向数字身份,来对抗盗用身份信息的现象。
数字身份是指代表个人在线的一组属性和标识符。这些可能包括姓名、电子邮件地址、电话号码、用户名和生物特征信息等等。数字身份验证是指验证这些属性是否准确以及是否属于它们声称代表的实体。
身份验证至关重要,因为它有助于在数字环境中建立信任,在数字环境中,面对面互动通常是不可能的。它确保参与交易的各方都是他们声称的人,最大程度地减少欺诈、身份盗窃和其他网络犯罪的风险。
数字身份包括两个部分
公司必须监控客户的每次交易和行为,然后使用存储的数字身份来评估 风险 ,识别给定交易的 可能的可疑活动 。
以下是对数字身份存储层的首要要求
这两个因素限制了使用传统的数据库管理系统 (RDBMS) 实时管理和验证数字身份。虽然可以使用 RDBMS 存储数字身份,但它不是用于实时验证灵活数据模型的最佳选择。
另一方面,Redis Cloud针对高吞吐量、低延迟、数据灵活性以及实时查询性能进行了优化,轻松满足了第一个标准。它具有 亚毫秒级延迟 ,每秒可以执行数亿次读写操作,非常适合管理动态数字身份数据。随着数据量的增长,我们可以期待接近线性的可扩展性和 99.999% 的正常运行时间,以及 Active-Active 地理复制 。
Redis Cloud 的灵活数据模型对多种数据类型提供原生支持,包括 JSON、哈希、流、图形等等 。此外,它可以处理对结构化和非结构化数据的复杂搜索,以及按数字属性和地理距离进行过滤,从而更容易管理和查询大型数字身份数据集。
本教程后面讨论的电子商务微服务应用程序使用以下架构
products service
: 处理从数据库查询产品并将其返回到前端orders service
: 处理验证和创建订单order history service
: 处理查询客户的订单历史记录payments service
: 处理订单的支付处理digital identity service
: 处理存储数字身份和计算身份得分api gateway
: 将服务统一到一个端点下mongodb/ postgresql
: 充当主数据库,存储订单、订单历史记录、产品等等。redis
: 充当 流处理器 和缓存数据库您不需要在演示应用程序中使用 MongoDB/ Postgresql 作为您的主数据库;您也可以使用其他 prisma 支持的数据库 。这只是一个示例。
鉴于我们正在讨论一个微服务应用程序,使用微服务来管理数字身份是有意义的。请考虑以下工作流程,概述数字身份如何存储和从 redis 中检索
演示应用程序没有 登录服务
。所有用户会话目前都在 API 网关
服务中进行身份验证。因此,就演示应用程序而言,登录服务
等同于 API 网关
。
演示应用程序使用 Redis 流进行服务间通信。以下是工作流程概述,以及每个服务的职责。
登录服务
: 将 (用户) 数字身份存储为 INSERT_LOGIN_IDENTITY
流项进入 Redis数字身份服务
: 从 INSERT_LOGIN_IDENTITY
流中读取身份数字身份服务
: 将身份以 JSON 格式存储到 Redis出于演示目的,我们只使用用户数字身份的几个特征,例如 IP 地址、浏览器指纹和会话。在实际应用中,您应该存储更多特征,例如位置、设备类型和之前采取的操作,以更好地进行风险评估和身份完整性。
在电子商务应用程序中,数字身份验证发生在结账时。您需要确保客户是他们自称的那个人,然后再尝试处理他们的订单。为了验证数字身份,我们需要计算数字身份得分,从 订单服务
开始。以下是工作流程概述,以及每个服务的职责。
订单服务
: 将数字身份存储到 CALCULATE_IDENTITY_SCORE
Redis 流事件中,以计算其身份得分。数字身份服务
: 从 CALCULATE_IDENTITY_SCORE
流事件中读取身份数字身份服务
: 将身份与其计算出的得分一起存储为 JSON即使您可能收到“1”的得分,但这仅仅意味着得分与已测量的属性完全匹配。我们只测量身份的数字方面,这些方面可能会受到损害。在实际场景中,您需要测量更多特征,例如位置、设备类型、会话等。这还需要其他上下文信息,以获得完整的 交易风险得分。
电子商务微服务应用程序包含一个前端,使用 Next.js 和 TailwindCSS 构建。应用程序后端使用 Node.js。数据存储在 Redis 和 MongoDB/Postgressql 中,使用 Prisma。您将在下面找到电子商务应用程序前端的屏幕截图。
仪表板
: 显示产品列表,并具有搜索功能订单历史记录
: 一旦订单下达,顶部导航栏中的“订单”链接会显示订单状态和历史记录以下是如何克隆本教程中使用的应用程序源代码的命令
git clone --branch v3.0.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions
现在,让我们一步一步地了解使用 Redis 存储、评分和验证数字身份的过程,并提供一些示例代码。出于演示目的,我们只使用用户数字身份的几个特征,例如 IP 地址、浏览器指纹和会话。在实际应用中,您应该存储更多特征,例如位置、设备类型和之前采取的操作,以更好地进行风险评估和身份完整性。
登录服务
: 将 (用户) 数字身份存储为 INSERT_LOGIN_IDENTITY
流项进入 Redis//addLoginToTransactionStream
const userId = 'USR_4e7acc44-e91e-4c5c-9112-bdd99d799dd3'; //from session
const sessionId = 'SES_94ff24a8-65b5-4795-9227-99906a43884e'; //from session
const persona = 'GRANDFATHER'; //from session
const entry: ITransactionStreamMessage = {
action: TransactionStreamActions.INSERT_LOGIN_IDENTITY,
logMessage: `[${REDIS_STREAMS.CONSUMERS.IDENTITY}] Digital identity to be stored for the user ${userId}`,
userId,
persona,
sessionId,
identityBrowserAgent: req.headers['user-agent'],
identityIpAddress:
req.headers['x-forwarded-for']?.toString() || req.socket.remoteAddress,
transactionPipeline: JSON.stringify(TransactionPipelines.LOGIN),
};
const nodeRedisClient = getNodeRedisClient();
const streamKeyName = 'TRANSACTION_STREAM';
const id = '*'; //* = auto generate
await nodeRedisClient.xAdd(streamKeyName, id, entry);
2. 数字身份服务
: 从 INSERT_LOGIN_IDENTITY
流中读取身份
interface ListenStreamOptions {
streams: {
streamKeyName: string;
eventHandlers: {
[messageAction: string]: IMessageHandler;
};
}[];
groupName: string;
consumerName: string;
maxNoOfEntriesToReadAtTime?: number;
}
// Below is some code for how you would use redis to listen for the stream events:
const listenToStreams = async (options: ListenStreamOptions) => {
/*
(A) create consumer group for the stream
(B) read set of messages from the stream
(C) process all messages received
(D) trigger appropriate action callback for each message
(E) acknowledge individual messages after processing
*/
const nodeRedisClient = getNodeRedisClient();
if (nodeRedisClient) {
const streams = options.streams;
const groupName = options.groupName;
const consumerName = options.consumerName;
const readMaxCount = options.maxNoOfEntriesToReadAtTime || 100;
const idInitialPosition = '0'; //0 = start, $ = end or any specific id
const streamKeyIdArr: {
key: string;
id: string;
}[] = [];
streams.map(async (stream) => {
LoggerCls.info(
`Creating consumer group ${groupName} in stream ${stream.streamKeyName}`,
);
try {
// (A) create consumer group for the stream
await nodeRedisClient.xGroupCreate(
stream.streamKeyName,
groupName,
idInitialPosition,
{
MKSTREAM: true,
},
);
} catch (err) {
LoggerCls.error(
`Consumer group ${groupName} already exists in stream ${stream.streamKeyName}!`,
);
}
streamKeyIdArr.push({
key: stream.streamKeyName,
id: '>', // Next entry ID that no consumer in this group has read
});
});
LoggerCls.info(`Starting consumer ${consumerName}.`);
while (true) {
try {
// (B) read set of messages from different streams
const dataArr = await nodeRedisClient.xReadGroup(
commandOptions({
isolated: true,
}),
groupName,
consumerName,
//can specify multiple streams in array [{key, id}]
streamKeyIdArr,
{
COUNT: readMaxCount, // Read n entries at a time
BLOCK: 5, //block for 0 (infinite) seconds if there are none.
},
);
// dataArr = [
// {
// name: 'streamName',
// messages: [
// {
// id: '1642088708425-0',
// message: {
// key1: 'value1',
// },
// },
// ],
// },
// ];
//(C) process all messages received
if (dataArr && dataArr.length) {
for (let data of dataArr) {
for (let messageItem of data.messages) {
const streamKeyName = data.name;
const stream = streams.find(
(s) => s.streamKeyName == streamKeyName,
);
if (stream && messageItem.message) {
const streamEventHandlers = stream.eventHandlers;
const messageAction = messageItem.message.action;
const messageHandler = streamEventHandlers[messageAction];
if (messageHandler) {
// (D) trigger appropriate action callback for each message
await messageHandler(messageItem.message, messageItem.id);
}
//(E) acknowledge individual messages after processing
nodeRedisClient.xAck(streamKeyName, groupName, messageItem.id);
}
}
}
} else {
// LoggerCls.info('No new stream entries.');
}
} catch (err) {
LoggerCls.error('xReadGroup error !', err);
}
}
}
};
// `listenToStreams` listens for events and calls the appropriate callback to further handle the events.
listenToStreams({
streams: [
{
streamKeyName: REDIS_STREAMS.STREAMS.TRANSACTIONS,
eventHandlers: {
[TransactionStreamActions.INSERT_LOGIN_IDENTITY]: insertLoginIdentity,
//...
},
},
],
groupName: REDIS_STREAMS.GROUPS.IDENTITY,
consumerName: REDIS_STREAMS.CONSUMERS.IDENTITY,
});
3. 数字身份服务
: 将身份以 JSON 格式存储到 Redis
const insertLoginIdentity: IMessageHandler = async (
message: ITransactionStreamMessage,
messageId,
) => {
LoggerCls.info(`Adding digital identity to redis for ${message.userId}`);
// add login digital identity to redis
const insertedKey = await addDigitalIdentityToRedis(message);
//...
};
const addDigitalIdentityToRedis = async (
message: ITransactionStreamMessage,
) => {
let insertedKey = '';
const userId = message.userId;
const digitalIdentity: IDigitalIdentity = {
action: message.action,
userId: userId,
sessionId: message.sessionId,
ipAddress: message.identityIpAddress,
browserFingerprint: crypto
.createHash('sha256')
.update(message.identityBrowserAgent)
.digest('hex'),
identityScore: message.identityScore ? message.identityScore : '',
createdOn: new Date(),
createdBy: userId,
statusCode: DB_ROW_STATUS.ACTIVE,
};
const repository = digitalIdentityRepo.getRepository();
if (repository) {
const entity = repository.createEntity(digitalIdentity);
insertedKey = await repository.save(entity);
}
return insertedKey;
};
订单服务
: 将要验证的数字身份存储到 CALCULATE_IDENTITY_SCORE
Redis 流中//adding Identity To TransactionStream
const userId = 'USR_4e7acc44-e91e-4c5c-9112-bdd99d799dd3';
const sessionId = 'SES_94ff24a8-65b5-4795-9227-99906a43884e';
let orderDetails = {
orderId: '63f5f8dc3696d145a45775a6',
orderAmount: '1000',
userId: userId,
sessionId: sessionId,
orderStatus: 1,
products: order.products, //array of product details
};
const entry: ITransactionStreamMessage = {
action: 'CALCULATE_IDENTITY_SCORE',
logMessage: `Digital identity to be validated/ scored for the user ${userId}`,
userId: userId,
sessionId: sessionId,
orderDetails: orderDetails ? JSON.stringify(orderDetails) : '',
transactionPipeline: JSON.stringify(TransactionPipelines.CHECKOUT),
identityBrowserAgent: req.headers['user-agent'],
identityIpAddress:
req.headers['x-forwarded-for']?.toString() || req.socket.remoteAddress,
};
const nodeRedisClient = getNodeRedisClient();
const streamKeyName = 'TRANSACTION_STREAM';
const id = '*'; //* = auto generate
await nodeRedisClient.xAdd(streamKeyName, id, entry);
2. 数字身份服务
从 CALCULATE_IDENTITY_SCORE
流中读取身份
listenToStreams({
streams: [
{
streamKeyName: REDIS_STREAMS.STREAMS.TRANSACTIONS,
eventHandlers: {
// ...
[TransactionStreamActions.CALCULATE_IDENTITY_SCORE]:
scoreDigitalIdentity,
},
},
],
groupName: REDIS_STREAMS.GROUPS.IDENTITY,
consumerName: REDIS_STREAMS.CONSUMERS.IDENTITY,
});
const scoreDigitalIdentity: IMessageHandler = async (
message: ITransactionStreamMessage,
messageId,
) => {
LoggerCls.info(`Scoring digital identity for ${message.userId}`);
//step 1 - calculate score for validation digital identity
const identityScore = await calculateIdentityScore(message);
message.identityScore = identityScore.toString();
LoggerCls.info(`Adding digital identity to redis for ${message.userId}`);
//step 2 - add validation digital identity to redis
const insertedKey = await addDigitalIdentityToRedis(message);
// ...
};
const calculateIdentityScore = async (message: ITransactionStreamMessage) => {
// Compare the "digital identity" with previously stored "login identities" and determine the identity score
let identityScore = 0;
const repository = digitalIdentityRepo.getRepository();
if (message && message.userId && repository) {
let queryBuilder = repository
.search()
.where('userId')
.eq(message.userId)
.and('action')
.eq('INSERT_LOGIN_IDENTITY')
.and('statusCode')
.eq(DB_ROW_STATUS.ACTIVE);
//console.log(queryBuilder.query);
const digitalIdentities = await queryBuilder.return.all();
if (digitalIdentities && digitalIdentities.length) {
//if browser details matches -> +1 score
const matchBrowserItems = digitalIdentities.filter((_digIdent) => {
let identityBrowserAgentHash = crypto
.createHash('sha256')
.update(message.identityBrowserAgent)
.digest('hex');
return _digIdent.browserFingerprint == identityBrowserAgentHash;
});
if (matchBrowserItems.length > 0) {
identityScore += 1;
}
//if IP address matches -> +1 score
const matchIpAddressItems = digitalIdentities.filter((_digIdent) => {
return _digIdent.ipAddress == message.identityIpAddress;
});
if (matchIpAddressItems.length > 0) {
identityScore += 1;
}
}
}
//calculate average score
const noOfIdentityCharacteristics = 2; //2 == browserFingerprint, ipAddress
identityScore = identityScore / noOfIdentityCharacteristics;
return identityScore; // identityScore final value ranges between 0 (no match) and 1 (full match)
};
3. 数字身份服务
: 将身份与其得分一起以 JSON 格式存储到 Redis
const addDigitalIdentityToRedis = async (
message: ITransactionStreamMessage,
) => {
let insertedKey = '';
const userId = message.userId;
const digitalIdentity: IDigitalIdentity = {
action: message.action,
userId: userId,
sessionId: message.sessionId,
ipAddress: message.identityIpAddress,
browserFingerprint: crypto
.createHash('sha256')
.update(message.identityBrowserAgent)
.digest('hex'),
identityScore: message.identityScore ? message.identityScore : '',
createdOn: new Date(),
createdBy: userId,
statusCode: DB_ROW_STATUS.ACTIVE, //1
};
const repository = digitalIdentityRepo.getRepository();
if (repository) {
const entity = repository.createEntity(digitalIdentity);
insertedKey = await repository.save(entity);
}
return insertedKey;
};
现在,您已经了解了如何在微服务应用程序中使用 Redis 设置持续的数字身份监控和评分。这也被称为“动态数字身份监控”。动态数字身份会根据每次数字交易中可用的信息不断更新。通过分析这些交易,企业可以构建一个全面且最新的数字身份,其中包含静态和动态元素。然后,可以对这些身份进行评分,以确定它们对企业的风险。
除了提高安全性外,数字身份还可以改善客户体验。通过利用用户留下的数字足迹,企业可以提供更个性化的服务,并减少身份验证过程中的摩擦。
数字身份系统通常设计为可互操作且可扩展的,允许与各种应用程序和平台无缝集成。