Laravel 实现 API 通讯数据的加解密
在与APP的api通讯中,经常会将敏感数据进行加密传输,然后再解密进行处理。在项目开发的时候,也会封装方法去对数据进行加解密,那么我们在laravel中能否在保持代码优雅度的情况下实现加解密呢?
当然可以,下面是我通过middleware
和Response::macro
实现加解密的方案。
请求的解密
对请求数据进行处理,首选当然是在middleware
中进行。
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\ParameterBag;
class DecryptApiRequest
{
/**
* @var Encrypter
*/
protected $encrypter;
public function __construct(Encrypter $encrypter)
{
$this->encrypter = $encrypter;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
$content = $this->decrypt($request->getContent());
} catch (DecryptException $exception) {
return abort(403);
}
return $next($this->putIn($request, $content));
}
/**
* decrypt the content
* @param string $content
* @return string
*/
protected function decrypt(string $content)
{
return $this->encrypter->decrypt($content, false);
}
/**
* put the decrypt data into request
* @param Request $request
* @param string $content
* @return Request
*/
protected function putIn(Request $request, string $content)
{
if ($request->getContentType() === 'json') {
$request->setJson(new ParameterBag((array) json_decode($content, true)));
} else {
$request->attributes = new ParameterBag([$request->getContentType() => $content]);
}
return $request;
}
}
该中间件中添加了decrypt()
和putIn()
方法,其中decrypt()
用于处理数据的解密,putIn()
用于将解密后的数据放入Request
对象用,供后面访问使用。由于加密数据请求一般是采用文本的形式在body中进行发送,所以这里在获取到解密的数据之后,根据contentType
类型存入了Request
对象中,如果是json
则存入了 Request
的 json
属性中, 如果是其他类型,则以contentType
为键 content
为值的形式存入了attributes
中。
于是乎,解密完成了,并且也不会遗弃Request
对象,不会因为在Reqeust
中取不到解密后的数据而放弃他。
怎么获取数据?json
类型直接使用 Request::get($key)
;text
类型使用 Request::get('txt')
;
其他类型,Reqeust::getContentType()
看看就知道了。
响应的加密
这里用的是响应宏进行了处理。具体请看响应的文档 【响应宏】
代码如下:
namespace App\Providers;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Response::macro('encrypt', function ($value) {
if (is_array($value)) $value = json_encode($value);
if ($value instanceof Jsonable) $value = $value->toJson();
return Response::make(app()->make(Encrypter::class)
->encrypt((string) $value, false));
});
}
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
}
在加密数据之前,首先对数据类型进行了处理,最终会将数据转化为string
类型进行加密。
使用时,直接调用encrypt()
方法即可。
Route::post('/', function (\Illuminate\Http\Request $request) {
return response()->encrypt(['a', 'b']);
});
本作品采用《CC 协议》,转载必须注明作者和本文链接
高认可度评论:
像这种和客户端(非机器)的通信,对称加密是没有意义的。因为密钥是公开的。
非对称加密才可行一点。比如rsa
app怎么解密呢?
@赵佳 看下laravel解密的源码,
Illuminate\Encryption\Encrypter
laravel默认使用的是这个驱动,主要是通过openssl_decrypt进行解码的,app端也有openssl加解密的包,不过key默认情况下是app_key中base64_decode之后的内容。其实在
middleware
中,$next($request)
返回的是一个Response
这里是指你没有修改middleware
中返回类型的情况,默认返回的就是你在控制器中返回的Response
。具体原理可参见Pipeline
的then()
和carry()
方法。所以在这里我们可以更加优美的进行处理,那就是直接在middleware
中封装一个encrypt()
方法,对Response
的content
进行加密,这样开发者就不需要关注是否需要调用response()->encrypt($data)
方法,而是直接返回response($data)
即可。更多方法,待大家去发现,这里的代码只作为参考,并没有做数据上的测试。
重点在于采用了那种加密算法
@赵聪 默认使用的是
AES-256-CBC
像这种和客户端(非机器)的通信,对称加密是没有意义的。因为密钥是公开的。
非对称加密才可行一点。比如rsa
@bluegeek 是的,应该用非对称加密
@bluegeek laravel 加密后还会再使用消息码签名的.
@ziyanziyu 签名的意义在于别人不知道你用来签名的密钥 其实和加密是一样的。用在客户端的一切都是公开的。所以签名也没有必要。签名都是用于机器和机器之间的。比如你可以看一下微信开发,微信服务器和自己的服务器,两边都是用同一个client secret验证的。而client secret别人是不知道的。
@bluegeek 在app通讯中使用非对称加密是好一些,这样appkey不会暴露出来,对项目的安全性也会很高。这里主要是说明如何使用中间件和响应宏进行处理加解密。如果要使用非对称加密,也可以自己封装一个非对称加密算法,注入到项目中。