在这个例子中,我们的客户端将使用OpenSSL来签署数据,然后再将其发送到服务器。当服务器收到数据时,它将使用OpenSSL来检查它是否用给定的签名签署过。如果验证成功,那么客户端就被认为是值得信赖的。在这个过程中,客户使用他的 "私钥",服务器使用客户的 "公钥 "进行验证。请看更多关于签署HTTP消息、openssl_sign和openssl_verify的信息。
流程
类别
签名控制器(SignatureController
我在这里非常懒惰,在控制器中做了太多的事情,但这纯粹是因为我想让帖子尽可能的简短。你可以把逻辑移到一个服务类中--只是一个例子。
namespace App\Controller;
use App\Util\SignatureUtil;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/signatures")
*/
class SignatureController
{
private $signatureUtil;
private $publicKey;
private $privateKey;
public function __construct(
SignatureUtil $signatureUtil,
string $publicKey,
string $privateKey
) {
$this->signatureUtil = $signatureUtil;
$this->publicKey = $publicKey;
$this->privateKey = $privateKey;
}
/**
* @Route("/client/create-keys", methods={"GET"})
*/
public function createKeys(): Response
{
$keys = $this->signatureUtil->createKeys();
file_put_contents($this->publicKey, $keys['public_key']);
file_put_contents($this->privateKey, $keys['private_key']);
return new JsonResponse('Done!');
}
/**
* @Route("/client/create-signature", methods={"POST"})
*/
public function createSignature(Request $request): Response
{
$privateKey = file_get_contents($this->privateKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $this->signatureUtil->createSignature($data, $privateKey);
return new JsonResponse($signature);
}
/**
* @Route("/server/verify-signature", methods={"POST"})
*/
public function verifySignature(Request $request): Response
{
$publicKey = file_get_contents($this->publicKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $request->headers->get('X-Signature');
$valid = $this->signatureUtil->verifySignature($data, $signature, $publicKey);
return new JsonResponse(['valid' => $valid]);
}
}
services:
...
App\Controller\SignatureController:
arguments:
$publicKey: '%kernel.project_dir%/config/ssl/public_key.pem'
$privateKey: '%kernel.project_dir%/config/ssl/private_key.pem'
-
/client/create-keys- 使用这个端点,以编程方式创建客户的公钥和私钥。然后把私钥给客户,公钥给服务器。**注意:**我添加这个端点只是为了演示,所以你不应该在现实世界中这样做。相反,在终端创建这些密钥。 -
/client/create-signature- 使用这个端点来签署你的数据。这个端点的响应将包含签名,它将和实际数据一起被发送到服务器。注意:假设这个端点是在客户端API中。 -
/server/verify-signature- 当客户端发送请求时,它将在这里登陆,签名验证在这里进行。注:假设这个端点是在服务器API中。
签章工具(SignatureUtil
请阅读代码中的注释:
namespace App\Util;
use RuntimeException;
class SignatureUtil
{
/**
* This is only relevant to server API.
*
* The "public_key" is shared with server.
* The "private_key" is kept securely by the client only.
*/
public function createKeys(): array
{
$keys = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
openssl_pkey_export($keys, $privateKey);
$keyDetails = openssl_pkey_get_details($keys);
$publicKey = $keyDetails['key'];
return [
'public_key' => $publicKey,
'private_key' => $privateKey,
];
}
/**
* This is only relevant to client API.
*
* The client calls this method to create a "signature" based on the "data".
* The client then sends the "signature" and the "data" to the server.
*/
public function createSignature(string $data, string $privateKey): string
{
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
/**
* This is only relevant to server API.
*
* The server checks "data" against the "signature" to see if the client is genuine or not.
*/
public function verifySignature(string $data, string $signature, string $publicKey): bool
{
$verify = openssl_verify($data, base64_decode($signature), $publicKey, 'sha256WithRSAEncryption');
if (1 == $verify) {
return true;
}
if (0 === $verify) {
return false;
}
throw new RuntimeException('An error occurred while verifying the signature.');
}
}
测试
创建客户端密钥
$ ls -l config/ssl/
total 0
$ curl -X GET \
> http://localhost/signatures/client/create-keys
"Done!"
$ ls -l config/ssl/
-rw-rw-rw- 1 501 dialout 1704 Jan 13 2019 private_key.pem
-rw-rw-rw- 1 501 dialout 451 Jan 13 2019 public_key.pem
创建签名
$ curl -X POST \
http://localhost/signatures/client/create-signature \
-H 'Content-Type: application/json' \
-d '{
"data": "Hello World!"
}'
"S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA=="
消耗API
在X-Signature header中使用签名:
$ curl -X POST \
> http://localhost/signatures/server/verify-signature \
> -H 'Content-Type: application/json' \
> -H 'X-Signature: S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA==' \
> -d '{
> "data": "Hello World!"
> }'
{"valid":true}