Rails自定义EncryptedTextType导致加载记录后始终标记为脏
问题成因
你遇到的是Rails自定义加密类型的典型脏检查误判问题,根因有两个:
- ActiveRecord对自定义类型字段的脏检查默认逻辑是:将当前内存中存储的明文属性值通过
serialize方法转为密文后,和数据库中存储的原始密文值做对比,二者相等才会判定为未变更。 - 工业级加密实现都会默认使用随机IV(初始化向量)/随机盐,保证相同明文每次加密得到的密文完全不同,所以即使你没修改过
access_key的明文,每次调用serialize得到的新密文永远和数据库中存储的旧密文不一致,就会被判定为字段变更。
你在changes输出中看到两个access_key值完全相同,是因为changes方法返回的是经过deserialize转换后的明文值,和底层脏检查用的密文判断维度不同。
修复方案
最优方案是重写自定义类型的changed_in_place?方法,直接对比明文的一致性,跳过密文对比逻辑,修改后的类型代码如下:
class EncryptedTextType < ActiveRecord::Type::Text def deserialize(encrypted_value) return unless encrypted_value Encryptor.decrypt(encrypted_value) end def serialize(plain_value) return unless plain_value Encryptor.encrypt(plain_value) end # 新增脏检查判断逻辑,直接对比明文是否修改 def changed_in_place?(raw_old_value, new_value) old_value = deserialize(raw_old_value) old_value != new_value end end ActiveRecord::Type.register(:encrypted_text, EncryptedTextType)
该方案不需要修改加密器逻辑,也不会影响加密安全性,修改后重启服务即可解决误判脏数据的问题。
内容的提问来源于stack exchange,提问作者Luiz E.
相关产品推荐
相关产品推荐

