我想有一些表单字段加密时,他们被添加/编辑和解密时,他们被蛋糕查找。以下是在v2.7.2中为我工作的代码:
core.php
Configure::write('Security.key','secretkey');
app/模型/patient.php。
public $encryptedFields = array('patient_surname', 'patient_first_name');
public function beforeSave($options = array()) {
foreach($this->encryptedFields as $fieldName){
if(!empty($this->data[$this->alias][$fieldName])){
$this->data[$this->alias][$fieldName] = Security::encrypt(
$this->data[$this->alias][$fieldName],
Configure::read('Security.key')
);
}
}
return true;
}
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
foreach($this->encryptedFields as $fieldName) {
if (@is_array($results[$key][$this->alias])) {
$results[$key][$this->alias][$fieldName] = Security::decrypt(
$results[$key][$this->alias][$fieldName],
Configure::read('Security.key')
);
}
}
}
return $results;
}
正如我所理解的,我必须将$this->data[]替换为模型生成的实体和afterFind方法与虚拟字段,但我只是不能把它们放在一起。
解决这个问题的方法不止一种(请注意,下面的代码是未经测试的示例代码!在使用这些之前,您应该首先掌握新的基础知识。
自定义数据库类型
一种是自定义数据库类型,它在将值绑定到数据库语句时进行加密,在获取结果时进行解密。这是我比较喜欢的选项。
下面是一个简单的例子,假设db列可以保存二进制数据。
src/数据库/类型/CryptedType.php
这应该是不言自明的,转换到数据库时加密,转换到PHP时解密。
<?php
namespace AppDatabaseType;
use CakeDatabaseDriver;
use CakeDatabaseType;
use CakeUtilitySecurity;
class CryptedType extends Type
{
public function toDatabase($value, Driver $driver)
{
return Security::encrypt($value, Security::getSalt());
}
public function toPHP($value, Driver $driver)
{
if ($value === null) {
return null;
}
return Security::decrypt($value, Security::getSalt());
}
}
src/config/bootstrap.php
注册自定义类型
use CakeDatabaseType;
Type::map('crypted', 'AppDatabaseTypeCryptedType');
src/模型/表/PatientsTable.php
最后将可加密列映射到注册的类型,就是这样,从现在开始,一切都将自动处理。
// ...
use CakeDatabaseSchemaTable as Schema;
class PatientsTable extends Table
{
// ...
protected function _initializeSchema(Schema $table)
{
$table->setColumnType('patient_surname', 'crypted');
$table->setColumnType('patient_first_name', 'crypted');
return $table;
}
// ...
}
参见Cookbook>数据库访问&ORM祝辞数据库基础;添加自定义类型
beforeSave和结果格式化器
一个不那么干燥和紧密耦合的方法,基本上是你的2的端口。x代码,将使用beforeSave
回调/事件和结果格式化程序。例如,结果格式化程序可以附加在beforeFind
事件/回调中。
在beforeSave
中,您可以使用Entity::has()
, Entity::get()
和Entity::set()
,甚至使用数组访问,因为实体实现ArrayAccess
。
结果格式化器基本上是一个after find钩子,您可以使用它轻松地遍历结果并修改它们。
这是一个基本的例子,不需要更多的解释:
// ...
use CakeEventEvent;
use CakeORMQuery;
class PatientsTable extends Table
{
// ...
public $encryptedFields = [
'patient_surname',
'patient_first_name'
];
public function beforeSave(Event $event, Entity $entity, ArrayObject $options)
{
foreach($this->encryptedFields as $fieldName) {
if($entity->has($fieldName)) {
$entity->set(
$fieldName,
Security::encrypt($entity->get($fieldName), Security::getSalt())
);
}
}
return true;
}
public function beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)
{
$query->formatResults(
function ($results) {
/* @var $results CakeDatasourceResultSetInterface|CakeCollectionCollectionInterface */
return $results->map(function ($row) {
/* @var $row array|CakeDataSourceEntityInterface */
foreach($this->encryptedFields as $fieldName) {
if(isset($row[$fieldName])) {
$row[$fieldName] = Security::decrypt($row[$fieldName], Security::getSalt());
}
}
return $row;
});
}
);
}
// ...
}
为了稍微解耦,您还可以将其移动到一个行为中,以便您可以轻松地跨多个模型共享它。
参见
- 食谱比;数据库访问&ORM祝辞数据库基础;添加自定义类型
- 食谱比;数据库访问&ORM祝辞查询生成器>新增计算字段
- 食谱比;教程,例子在书签教程第2部分;持久化标签字符串
- 食谱比;数据库访问&ORM祝辞行为
- API比;蛋糕 数据源 EntityTrait
- API比;表蛋糕 ORM
编辑:@npm关于虚拟属性不工作的说法是正确的。现在我很生自己的气,因为我回答得不好。我发帖前没有检查一下,真是活该。
为了使它正确,我实现了一个使用行为的版本,在读取字段时对其解密,并在将其写入数据库时对其加密。
注意:此代码目前不包含任何自定义查找器,因此它不支持通过加密字段进行搜索。
。
$this->Patient->findByPatientFirstname('bob'); // this will not work
/src/模型/行为/EncryptBehavior.php
<?php
/**
*
*/
namespace CakeORMBehavior;
use ArrayObject;
use CakeCollectionCollection;
use CakeDatasourceEntityInterface;
use CakeDatasourceResultSetInterface;
use CakeEventEvent;
use CakeORMBehavior;
use CakeORMEntity;
use CakeORMQuery;
use CakeORMTable;
use CakeORMTableRegistry;
use CakeUtilityInflector;
use CakeUtilitySecurity;
use CakeLogLog;
/**
* Encrypt Behavior
*/
class EncryptBehavior extends Behavior
{
/**
* Default config
*
* These are merged with user-provided configuration when the behavior is used.
*
* @var array
*/
protected $_defaultConfig = [
'key' => 'YOUR_KEY_KERE', /* set them in the EntityTable, not here */
'fields' => []
];
/**
* Before save listener.
* Transparently manages setting the lft and rght fields if the parent field is
* included in the parameters to be saved.
*
* @param CakeEventEvent $event The beforeSave event that was fired
* @param CakeORMEntity $entity the entity that is going to be saved
* @return void
* @throws RuntimeException if the parent to set for the node is invalid
*/
public function beforeSave(Event $event, Entity $entity)
{
$isNew = $entity->isNew();
$config = $this->config();
$values = $entity->extract($config['fields'], true);
$fields = array_keys($values);
$securityKey = $config['key'];
foreach($fields as $field){
if( isset($values[$field]) && !empty($values[$field]) ){
$entity->set($field, Security::encrypt($values[$field], $securityKey));
}
}
}
/**
* Callback method that listens to the `beforeFind` event in the bound
* table. It modifies the passed query
*
* @param CakeEventEvent $event The beforeFind event that was fired.
* @param CakeORMQuery $query Query
* @param ArrayObject $options The options for the query
* @return void
*/
public function beforeFind(Event $event, Query $query, $options)
{
$query->formatResults(function ($results){
return $this->_rowMapper($results);
}, $query::PREPEND);
}
/**
* Modifies the results from a table find in order to merge the decrypted fields
* into the results.
*
* @param CakeDatasourceResultSetInterface $results Results to map.
* @return CakeCollectionCollection
*/
protected function _rowMapper($results)
{
return $results->map(function ($row) {
if ($row === null) {
return $row;
}
$hydrated = !is_array($row);
$fields = $this->_config['fields'];
$key = $this->_config['key'];
foreach ($fields as $field) {
$row[$field] = Security::decrypt($row[$field], $key);
}
if ($hydrated) {
$row->clean();
}
return $row;
});
}
}
表/src/模型/表/PatientsTable.php
<?php
namespace AppModelTable;
use AppModelEntityPatient;
use CakeORMQuery;
use CakeORMRulesChecker;
use CakeORMTable;
use CakeValidationValidator;
use CakeCoreConfigure;
/**
* Patients Model
*
*/
class PatientsTable extends Table
{
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('patients');
$this->displayField('id');
$this->primaryKey('id');
// will encrypt these fields automatically
$this->addBehavior('Encrypt',[
'key' => Configure::read('Security.key'),
'fields' => [
'patient_surname',
'patient_firstname'
]
]);
}
}
我能感受到你的痛苦。cakephp 3中的ORM层与cake2完全不同。他们将实体模型和表ORM拆分为两个不同的类,并删除了afterFind。我会考虑使用虚拟属性。我想它可能适合你的用例。
<?php
namespace AppModelEntity;
use CakeORMEntity;
use CakeUtilitySecurity;
use CakeCoreConfigure;
class Patient extends Entity
{
protected function _setPatientSurname($str)
{
$this->set('patient_surname', Security::encrypt($str, Configure::read('Security.key'));
}
protected function _setPatientFirstname($str)
{
$this->set('patient_firstname', Security::encrypt($str, Configure::read('Security.key'));
}
protected function _getPatientSurname()
{
return Security::decrypt($this->patient_surname, Configure::read('Security.key'));
}
protected function _getPatientFirstname()
{
return Security::decrypt($this->patient_first_name, Configure::read('Security.key'));
}
}