CakePHP 3 中表单字段的加密/解密
我想在添加/编辑时加密一些表单字段,并在蛋糕查找它们时对其进行解密.这是在 v2.7.2 中对我有用的代码:
I want to have some form-fields encrypted when they are added/edited and decrypted when they are looked up by cake. Here is the code that works for me in v2.7.2:
core.php
Configure::write('Security.key','secretkey');
app/model/patient.php.
app/model/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 方法替换为虚拟字段,但我无法将它们放在一起.
As I understand it I have to replace $this->data[] with the generated entities for the model and the afterFind method with virtual fields, but I just can't put it all together.
推荐答案
解决这个问题的方法不止一种(请注意以下代码是未经测试的示例代码!在使用任何新的基础知识之前,您应该先掌握新的基础知识)这个).
There's more than one way to solve this (please note that the following code is untested example code! You should get a grasp on the new basics first before using any of this).
一种是自定义数据库类型,它会在将值绑定到数据库语句时进行加密,并在获取结果时进行解密.这是我更喜欢的选项.
One would be a custom database type, which would encrypt when binding the values to the database statement, and decrypt when results are being fetched. That's the option that I would prefer.
这是一个简单的例子,假设 db 列可以保存二进制数据.
Here's simple example, assuming the db columns can hold binary data.
src/Database/Type/CryptedType.php
这应该是不言自明的,转换到数据库时加密,转换到 PHP 时解密.
This should be rather self explantory, encrypt when casting to database, decrypt when casting to 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/Model/Table/PatientsTable.php
最后将可加密列映射到注册类型,就是这样,从现在开始,一切都会自动处理.
Finally map the cryptable columns to the registered type, and that's it, from now on everything's being handled automatically.
// ...
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;
}
// ...
}
参见食谱>数据库访问ORM >数据库基础添加自定义类型
See Cookbook > Database Access & ORM > Database Basics > Adding Custom Types
一种不那么枯燥和紧密耦合的方法,基本上是您 2.x 代码的一个端口,将使用 beforeSave
回调/事件和结果格式化程序.例如,结果格式化程序可以附加在 beforeFind
事件/回调中.
A less dry and tighter coupled approach, and basically a port of your 2.x code, would be to use the beforeSave
callback/event, and a result formatter. The result formatter could for example be attached in the beforeFind
event/callback.
在 beforeSave
中只需设置/获取传入实体实例的值,您可以使用 Entity::has()
, Entity::get()
和 Entity::set()
,甚至使用数组访问,因为实体实现了 ArrayAccess
.
In beforeSave
just set/get the values to/from the passed entity instance, you can utilize Entity::has()
, Entity::get()
and Entity::set()
, or even use array access since entities implement ArrayAccess
.
结果格式化程序基本上是一个 after find 钩子,您可以使用它轻松地迭代结果并修改它们.
The result formatter is basically an after find hook, and you can use it to easily iterate over results, and modify them.
这是一个基本的例子,不需要太多解释:
Here's a basic example, which shouldn't need much further explanation:
// ...
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;
});
}
);
}
// ...
}
为了稍微解耦,您还可以将其移动到行为中,以便您可以轻松地在多个模型之间共享它.
To decouple this a little, you could also move this into a behavior so that you can easily share it across multiple models.
另见
- 食谱>数据库访问ORM >数据库基础添加自定义类型
- 食谱>数据库访问ORM >查询生成器添加计算字段
- 食谱>教程 &例子 >书签教程第 2 部分 >保留标签字符串
- 食谱>数据库访问ORM >行为
- API >CakeDatasourceEntityTrait
- API >CakeORMTable
相关文章