第三章 令牌存储与映射

令牌发出去之后,怎么跟原始数据对上号?说白了就是存个映射关系。这个环节要是搞砸了,令牌就形同虚设。我之前在金融项目里踩过坑——映射表设计得太随意,结果泄露了一批用户身份证号。嗯,今天咱们就把这块彻底讲透。

3.1 映射关系:一对一还是多对一?

令牌和原始数据之间,最常见的是一对一映射。一个令牌对应一条原始数据,比如用户的信用卡号或者手机号。但有些场景下,一个用户可能有多个令牌指向同一份敏感数据(例如多设备登录),这时候就是多对一的。

我的习惯:除非业务明确要求多令牌共享数据,否则一律用一对一。多对一容易导致吊销混乱——你吊销了一个令牌,其他令牌还活着,但原始数据可能已经失效了。

映射表里的核心字段就这么几个:

  • 令牌ID - 全局唯一,通常用UUID或自增ID
  • 令牌哈希 - 存储令牌的哈希值,而不是明文令牌(后面会讲原因)
  • 原始数据 - 加密后存储,比如AES加密后的身份证号
  • 过期时间 - 令牌的TTL,过了时间自动失效
  • 用户ID - 可选的,方便按用户注销所有令牌
  • 创建时间 - 审计用

3.2 映射表设计:这么设计最稳

以MySQL为例,实打实的建表语句:

CREATE TABLE _mapping (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    _hash VARCHAR(128) NOT NULL UNIQUE,
    encrypted_data BLOB NOT NULL,
    expire_at DATETIME NOT NULL,
    user_id VARCHAR(64),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_expire_at (expire_at),
    INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意 _hash 设了UNIQUE,防止同一个哈希重复插入。而且 expire_at 建了索引,清理过期令牌时效率高。

要是用Redis呢?更轻量:

// 存储映射(过期时间自动处理)
SET :<令牌哈希> <加密后的原始数据> EX 3600

// 读取映射
GET :<令牌哈希>

数据库和KV存储各有优缺,直接对比:

维度 关系数据库(MySQL/PostgreSQL) KV存储(Redis/Memcached)
持久性 强,数据不丢 取决于配置,可持久化但一般做缓存
查询能力 支持条件查询(如按用户ID查所有令牌) 只能按key精确查询
性能 毫秒级,但高并发需连接池 微秒级,极低延迟
自动过期 需要后台脚本清理 原生支持TTL
适用场景 需要审计、多条件查询的令牌 高吞吐、临时令牌、会话令牌

我曾经犯过的错:在一家电商公司,我把所有令牌映射都放进了Redis,没有做持久化。结果一次节点重启,上百万个令牌全部丢失,用户全部需要重新登录。后来我在Redis前面加了一层Redis Cluster + AOF持久化,同时在MySQL里保留了一份备份(异步同步)。

3.3 映射安全:不能让人顺着令牌查到原始数据

映射表如果被拖库,后果就是所有原始数据泄露。你必须做好这几点:

  • 存储令牌的哈希,而非明文。客户端传令牌过来,你哈希后再查表。
  • 原始数据必须加密,推荐AES-256-GCM。密钥跟令牌服务分离,最好放在KMS里。
  • 映射表本身加访问控制:只允许令牌服务进程访问,禁止任何人直接连数据库。
  • 日志脱敏:不要在日志里打印令牌、哈希或原始数据。

令牌哈希用什么算法?我个人习惯用SHA-256+盐。你想想看,如果两个令牌哈希相同怎么办?概率极低,但你还是要在数据库层面加UNIQUE约束,捕住它。

顺便提一下双令牌机制(访问令牌+刷新令牌),很多架构喜欢用。但这并不降低映射表的安全要求——刷新令牌里依然藏着用户身份,映射表该保护的一分不能少。

3.4 整体结构:一张SVG讲清楚

下面这张图展示了令牌存储与映射的核心流程,注意安全边界在哪里:

令牌存储与映射核心架构 令牌存储与映射核心架构 客户端 令牌服务 映射表存储 (DB / Redis) 原始数据存储 (加密后的敏感数据) 请求令牌 写入/查询映射 获取加密数据 禁止直接访问 安全边界 说明: 1. 令牌服务只能通过映射表访问原始数据,无法直接读取原始存储。 2. 映射表中存储的是令牌哈希和加密后的原始数据,双重保护。 3. 安全边界内所有通信必须加密(TLS),且仅授权服务可进出。

记住,映射表是整个令牌化服务中最核心的资产。你保护好了它,令牌化才真正有意义。