随着数字环境不断发展,保护用户和组织免受侵害的强大安全措施的需求变得愈发重要。数字身份验证和欺诈检测是任何全面安全计划的关键组成部分。本文将探讨数字身份验证的重要性、该领域面临的挑战以及使用 Redis 进行数字身份验证的解决方案。
“了解您的客户”(KYC) 规定是指金融机构和其他受监管企业必须遵守的一系列政策和程序,用于验证其客户的身份。客户详情包括姓名、地址、出生日期以及其他政府颁发的身份证明文件。
作为 KYC 的一部分,企业必须评估每位客户可能带来的风险,并对其交易和行为进行 持续监控,以检测任何可疑活动。KYC 规定由监管机构强制执行,不遵守规定可能导致罚款和声誉损害。
KYC 规定旨在防止洗钱、恐怖融资和其他非法活动。金融服务公司正在通过减少对静态身份验证方法(基于知识的验证,即 KBA)的依赖,转而采用数字身份,来打击被盗身份信息的使用。
数字身份是指代表个人在线的属性和标识符的集合。这可能包括姓名、电子邮件地址、电话号码、用户名和生物识别信息等。数字身份验证是验证这些属性准确无误并属于其声称代表的实体的过程。
身份验证至关重要,因为它有助于在数字环境中建立信任,而数字环境中通常无法进行面对面互动。它确保交易参与方是他们声称的身份,从而最大程度地降低欺诈、身份盗用和其他网络犯罪的风险。
数字身份由两部分组成
公司必须监控客户的每一笔交易和行为,然后使用存储的数字身份对 风险进行评分,从而识别给定交易的 可能可疑活动。
数字身份存储层的主要要求如下
对于使用传统关系数据库管理系统 (RDBMS) 来管理和验证数字身份而言,这两个因素是限制。虽然可以使用 RDBMS 存储数字身份,但对于灵活数据模型的实时验证而言,它并不是最佳选择。
另一方面,Redis Cloud 经过优化,具有高吞吐量、低延迟、数据灵活性和实时查询性能,轻松满足第一个标准。凭借 亚毫秒级延迟和每秒数亿次的读写操作,它非常适合管理动态数字身份数据。随着数据量的增长,我们可以期待近乎线性的可伸缩性和通过 主-主地理复制实现 99.999% 的正常运行时间。
Redis Cloud 灵活的数据模型原生支持多种数据类型,包括 JSON、哈希、流、图等。此外,它还可以处理对结构化和非结构化数据的复杂搜索,以及按数值属性和地理距离进行过滤,从而更轻松地管理和查询大型数字身份数据集。
本教程其余部分讨论的电子商务微服务应用使用以下架构
products service
:处理从数据库查询产品并将其返回给前端orders service
:处理订单验证和创建order history service
:处理查询客户的订单历史记录payments service
:处理订单支付digital identity service
:处理数字身份存储和身份评分计算api gateway
:在单个端点下统一服务mongodb/ postgresql
:作为主要数据库,存储订单、订单历史记录、产品等redis
:作为 流处理器和缓存数据库在演示应用中,您不需要使用 MongoDB/ Postgresql 作为主要数据库;您也可以使用其他 支持 prisma 的数据库。这只是一个示例。
鉴于我们正在讨论微服务应用,使用微服务来管理数字身份是合理的。请考虑以下工作流程,它概述了数字身份如何存储和从 Redis 中检索
演示应用没有 login service
。所有用户会话目前都在 api gateway
服务中进行身份验证。因此,对于演示应用而言, login service
与 api gateway
是同义的。
演示应用使用 Redis streams 进行服务间通信。以下概述了工作流程以及每个服务的职责
login service
:将(用户)数字身份作为 INSERT_LOGIN_IDENTITY
流条目存储到 Redisdigital identity service
:从 INSERT_LOGIN_IDENTITY
流中读取身份digital identity service
:将身份以 JSON 格式存储到 Redis出于演示目的,我们仅使用用户数字身份的几个特征,例如 IP 地址、浏览器指纹和会话。在实际应用中,您应该存储更多特征,例如位置、设备类型和先前采取的操作,以进行更好的风险评估和身份完整性。
在电子商务应用中,数字身份验证发生在结账时。在尝试处理客户订单之前,您需要确保客户是他们声称的身份。要验证数字身份,我们需要计算数字身份得分,这从 orders service
开始。以下概述了工作流程以及每个服务的职责
orders service
:将数字身份存储在 CALCULATE_IDENTITY_SCORE
redis 流事件中,以计算其身份得分 digital identity service
:从 CALCULATE_IDENTITY_SCORE
流事件中读取身份digital identity service
:将身份及其计算出的得分以 JSON 格式存储即使您可能收到“1”的分数,这仅意味着该分数仅与衡量的属性 100% 匹配。我们只测量身份的数字方面,这可能会受到损害。在实际场景中,您会希望测量更多特征,例如位置、设备类型、会话等。此外,还需要结合其他上下文信息以获得完整的 交易风险评分。
电子商务微服务应用包含一个前端,使用 Next.js 和 TailwindCSS构建。应用后端使用 Node.js。数据使用 Prisma存储在 Redis和 MongoDB/ Postgressql 中。下方是电子商务应用前端的截图
Dashboard
:显示产品列表和搜索功能订单历史记录
:下订单后,顶部导航栏中的“订单”链接会显示订单状态和历史记录以下是用于克隆本教程中使用的应用程序源代码的命令
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 设置持续的数字身份监控和评分。这也被称为“动态数字身份监控”。动态数字身份会根据每次数字交易中获得的信息不断更新。通过分析这些交易,企业可以构建一个全面且最新的数字身份,其中包括静态和动态元素。然后可以对这些身份进行评分,以确定它们对业务造成的风险。
除了提高安全性之外,数字身份还可以改善客户体验。通过利用用户留下的数字足迹,企业可以提供更加个性化的服务,并减少身份验证过程中的摩擦。
数字身份系统通常设计为可互操作和可扩展,可以与各种应用和平台无缝集成。
Redis Streams
使用 Redis 进行欺诈检测
通用