Laravel 实现 API 通讯数据的加解密

在与 APP 的 api 通讯中,经常会将敏感数据进行加密传输,然后再解密进行处理。在项目开发的时候,也会封装方法去对数据进行加解密,那么我们在 laravel 中能否在保持代码优雅度的情况下实现加解密呢?
当然可以,下面是我通过 middlewareResponse::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 则存入了 Requestjson 属性中, 如果是其他类型,则以 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 协议》,转载必须注明作者和本文链接
打酱油
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 10

像这种和客户端 (非机器) 的通信,对称加密是没有意义的。因为密钥是公开的。
非对称加密才可行一点。比如 rsa

6年前 评论
赵佳

app 怎么解密呢?

6年前 评论

@赵佳 看下 laravel 解密的源码,Illuminate\Encryption\Encrypter laravel 默认使用的是这个驱动,主要是通过 openssl_decrypt 进行解码的,app 端也有 openssl 加解密的包,不过 key 默认情况下是 app_key 中 base64_decode 之后的内容。

6年前 评论

其实在 middleware 中,$next($request) 返回的是一个 Response 这里是指你没有修改 middleware 中返回类型的情况,默认返回的就是你在控制器中返回的 Response。具体原理可参见 Pipelinethen()carry() 方法。所以在这里我们可以更加优美的进行处理,那就是直接在 middleware 中封装一个 encrypt() 方法,对 Responsecontent 进行加密,这样开发者就不需要关注是否需要调用 response()->encrypt($data) 方法,而是直接返回 response($data) 即可。
更多方法,待大家去发现,这里的代码只作为参考,并没有做数据上的测试。

6年前 评论
农村闲散劳动力

重点在于采用了那种加密算法

6年前 评论

@赵聪 默认使用的是 AES-256-CBC

6年前 评论

像这种和客户端 (非机器) 的通信,对称加密是没有意义的。因为密钥是公开的。
非对称加密才可行一点。比如 rsa

6年前 评论

@bluegeek 是的,应该用非对称加密

6年前 评论
wenber

@bluegeek laravel 加密后还会再使用消息码签名的.

6年前 评论

@ziyanziyu 签名的意义在于别人不知道你用来签名的密钥 其实和加密是一样的。用在客户端的一切都是公开的。所以签名也没有必要。签名都是用于机器和机器之间的。比如你可以看一下微信开发,微信服务器和自己的服务器,两边都是用同一个 client secret 验证的。而 client secret 别人是不知道的。

6年前 评论

@bluegeek 在 app 通讯中使用非对称加密是好一些,这样 appkey 不会暴露出来,对项目的安全性也会很高。这里主要是说明如何使用中间件和响应宏进行处理加解密。如果要使用非对称加密,也可以自己封装一个非对称加密算法,注入到项目中。

6年前 评论