HOOK-28: fixed signature format (#16)
* HOOK-28: fixed signature format * fixed keysize and readme * fixed minor bug * fixed base64 encoder and decoder to url safe version * up pom version
This commit is contained in:
parent
c5f4a5beb0
commit
7dca3749fc
60
README.md
60
README.md
@ -4,75 +4,25 @@
|
||||
[![Build Status](http://ci.rbkmoney.com/buildStatus/icon?job=rbkmoney_private/hooker/master)](http://ci.rbkmoney.com/job/rbkmoney_private/job/hooker/job/master/)
|
||||
|
||||
1. Сервис предоставляет интерфейс для CAPI для создания, удаления вебхуков, установки/редактирования опций вебхука (таких как url, public/private keys - они генерируются сервисом, типы событий).
|
||||
2. Сервис поллит bustermaze на появление событий, по которым должны отправляться сообщения на вебхуки
|
||||
2. Сервис поллит bustermaze на появление событий, по которым должны отправляться сообщения мерчантам
|
||||
|
||||
Интерфейс для capi доступен по пути /hook
|
||||
|
||||
Для более подробного ознакомления со структурой объектов можно воспользоваться ссылкой на сам объект [webhooker.thrift][1]
|
||||
|
||||
### Типы событий, по которым отправляются сообщения на вебхуки
|
||||
|
||||
Сообщения отправляются на следующие события:
|
||||
|
||||
1. Создание инвойса
|
||||
2. Изменение статуса инвойса
|
||||
3. Создание платежа
|
||||
4. Изменение статуса платежа
|
||||
|
||||
### Формат сообщения, отправляемого мерчанту
|
||||
|
||||
#### Пример отправки запроса мерчанту
|
||||
|
||||
###### Отправка данных
|
||||
Запрос в формате JSON отправляется методом POST на url, указанный при создании вебхука.
|
||||
Подпись идет в headers запроса.
|
||||
|
||||
Тело запроса содержит поля (все поля, кроме payment_id, обязательные):
|
||||
|
||||
Название | Описание | Пример
|
||||
------------ | ------------- | -------------
|
||||
**event_type** | тип эвента (инвойс или платеж) | invoice (payment)
|
||||
**invoice_id** | номер инвойса | 2Dbs4d4Dw
|
||||
**payment_id** | номер платежа | fsee5562
|
||||
**shop_id** | идентификатор магазина | 55
|
||||
**amount** | сумма, в минорных единицах | 120000
|
||||
**currency** | валюта (ISO 4217)| RUB
|
||||
**created_at** | дата и время создания инвойса в формате UTC (RFC 3339) | 2011-07-01T09:00:00Z
|
||||
**metadata** | данные, переданные мерчантом при создании инвойса | {"order_id":"my_order_id"}
|
||||
**status** | статус инвойса(платежа) | для инвойса (unpaid, paid, cancelled, fulfilled); для платежа (pending, processed, captured, cancelled, failed)
|
||||
|
||||
Таймаут на соединение и на запрос составляет 10с.
|
||||
|
||||
[Cписок кодов состояния HTTP][3]
|
||||
|
||||
|
||||
#### Пример формирования подписи
|
||||
Тело запроса (JSON) подписывается и подпись добавляется как заголовок http-запроса. Имя заголовка - X-Signature.
|
||||
Тело запроса для события "Инвойс создан" будет выглядеть так:
|
||||
```
|
||||
{"event_type":"invoice","invoice_id":"45b69f6ab0","payment_id":null,"shop_id":1,"amount":6207,"currency":"RUB","created_at":"2017-04-10T21:53:09.271Z","metadata":{"type":"contentType","data":"dGVzdA==","fields":["TYPE","DATA"],"fieldMetaData":{"TYPE":{"fieldName":"type","requirementType":1,"valueMetaData":{"type":11,"typedefName":null,"binary":false,"container":false,"struct":false,"typedef":false}},"DATA":{"fieldName":"data","requirementType":1,"valueMetaData":{"type":11,"typedefName":null,"binary":true,"container":false,"struct":false,"typedef":false}}},"setType":true,"setData":true},"status":"unpaid"}
|
||||
```
|
||||
После чего строка подписывается приватным ключом RSA с алгоритмом хэширования SHA-256 и размером ключа 1024.
|
||||
Подробная информация по протоколу отправки сообщений мерчантам описана в [swagger-спецификации][2]
|
||||
|
||||
#### Пример запроса к мерчанту
|
||||
|
||||
```
|
||||
curl -v -X POST
|
||||
-H "Content-Type: application/json; charset=utf-8"
|
||||
-H "cor2A+pQ9dOMwUq8U5xUeLwh9UQD3DtzHHxcC5pplChgwmRKH5LG9Wz/rnZ2GLwUzLelowisVre1aHUKGJSl/NSF0PWJSWfCbMVtKpMwA9ZRb4pcKVX/RBiczCUIX+gFVj/6G7qWXBCxbcBV/u/vn61rWFBYi1Knvmaov4kGvZo="
|
||||
-d '{"event_type":"invoice","invoice_id":"45b69f6ab0","payment_id":null,"shop_id":1,"amount":6207,"currency":"RUB","created_at":"2017-04-10T21:53:09.271Z","metadata":{"type":"contentType","data":"dGVzdA==","fields":["TYPE","DATA"],"fieldMetaData":{"TYPE":{"fieldName":"type","requirementType":1,"valueMetaData":{"type":11,"typedefName":null,"binary":false,"container":false,"struct":false,"typedef":false}},"DATA":{"fieldName":"data","requirementType":1,"valueMetaData":{"type":11,"typedefName":null,"binary":true,"container":false,"struct":false,"typedef":false}}},"setType":true,"setData":true},"status":"unpaid"}
|
||||
-H "Content-Signature: alg=RS256; digest=QydtAwZ8jmmrX1wP87vAJIXVBiRZe3Zo5xRYnzMQIsDxxsVn26XGMBeD4op8_9LmXIAirYWd53gfX638ZR83D2pTSCXkNpzHyqiyzhPlH2asaC9qiiOOWaQ-kHk9xFlOcMa1Qtt0BaVst-tbGDsFhgjl6tvGuN4JHRq5khzl_iIJ_ZQniEuzGOYReWn8wCLmaspX0MPyACMTo8HDwyihOHB1_RQBliBFYyvw523xvSQ6WxWYjYhFsjglIg1wdKUMyaiScw0kmKm53OdxDAnjl4MPQmtryuANjbklN8_EatOrQAqGwRUp1ayR_3WMlayhpxaEHlG1sAHQaaO3ulI35g=="
|
||||
-d '{"eventID":27,"occuredAt":"2017-05-16T13:49:34.935099Z","topic":"InvoicesTopic","eventType":"PaymentCaptured","invoice":{"id":"qXMiygTqb2","shopID":1,"createdAt":"2017-05-16T13:49:32.753723Z","status":"unpaid","reason":null,"dueDate":"2017-05-16T13:59:32Z","amount":100000,"currency":"RUB","metadata":{"type":"application/json","data":"eyJpbnZvaWNlX2R1bW15X2NvbnRleHQiOiJ0ZXN0X3ZhbHVlIn0="},"product":"test_product","description":"test_invoice_description"},"payment":{"id":"1","createdAt":"2017-05-16T13:49:33.182195Z","status":"captured","error":null,"amount":100000,"currency":"RUB","paymentToolToken":"5Gz2nhE1eleFGBAcGe9SrA","paymentSession":"2nTYVgk6h85O7vIVV9j4pA","contactInfo":{"email":"bla@bla.ru","phoneNumber":null},"ip":"10.100.2.1","fingerprint":"test fingerprint"}}
|
||||
' https://{host}:{port}/{path}
|
||||
```
|
||||
|
||||
Мерчант, используя публичный ключ и имея в распоряжении тело запроса, подпись, алгоритм подписи и хэширования, может произвести проверку подписи
|
||||
|
||||
#### Политика переотправки сообщений
|
||||
Если хук не отвечает или отвечает с ошибкой, пробуем 4 раза с интервалами 30сек, 5мин, 15мин, 1час опять послать
|
||||
неотправленное сообщение в этот хук. При этом очередь сообщений для хука копится.
|
||||
Если и четвертая попытка отправить сообщение оканчивается неудачей, хук помечается как выключеный и очередь сообщений для него сбрасывается.
|
||||
Больше попыток отправить туда сообщения не предпринимается.
|
||||
|
||||
|
||||
В текущей реализации хукера есть возможность устанавливать разные политики для разных хуков, но имплементирована только одна политика, которую легко изменить.
|
||||
|
||||
[1]: https://github.com/rbkmoney/damsel/blob/master/proto/webhooker.thrift
|
||||
[2]: https://github.com/rbkmoney/swag-webhook-events/blob/master/spec/swagger.yaml
|
||||
|
2
pom.xml
2
pom.xml
@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>hooker</artifactId>
|
||||
<version>1.1.0-SNAPSHOT</version>
|
||||
<version>1.1.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>hooker</name>
|
||||
|
@ -30,7 +30,7 @@ public class PostSender {
|
||||
RequestBody body = RequestBody.create(JSON, paramsAsString);
|
||||
final Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader(SIGNATURE_HEADER, signature)
|
||||
.addHeader(SIGNATURE_HEADER, "alg=RS256; digest="+signature)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
|
@ -13,7 +13,7 @@ import java.util.Base64;
|
||||
public class AsymSigner implements Signer {
|
||||
public static final String KEY_ALGORITHM = "RSA";
|
||||
public static final String HASH_ALGORITHM = "SHA256withRSA";
|
||||
public static final int KEYSIZE = 1024;
|
||||
public static final int KEYSIZE = 2048;
|
||||
|
||||
private KeyFactory keyFactory;
|
||||
private Signature sig;
|
||||
@ -44,7 +44,7 @@ public class AsymSigner implements Signer {
|
||||
sig.update(data.getBytes());
|
||||
signatureBytes = sig.sign();
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(signatureBytes);
|
||||
return Base64.getUrlEncoder().encodeToString(signatureBytes);
|
||||
} catch (InvalidKeySpecException | InvalidKeyException | SignatureException e) {
|
||||
throw new UnknownCryptoException(e);
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ public class AsymSignerTest {
|
||||
public void test() throws Exception {
|
||||
AsymSigner asymSigner = new AsymSigner();
|
||||
KeyPair keyPair = asymSigner.generateKeys();
|
||||
String data = "{\"invoice_id\":\"2Dbs4d4Dw\",\"amount\":120000,\"currency\":\"RUB\",\"created_at\":\"2011-07-01T09:00:00Z\",\"context\":{\"type\":null,\"data\":\"eyJvcmRlcl9pZCI6Im15X29yZGVyX2lkIn0=\",\"setType\":false,\"setData\":true},\"status\":\"PAID\"}";
|
||||
String data = "{\"eventID\":27,\"occuredAt\":\"2017-05-16T13:49:34.935099Z\",\"topic\":\"InvoicesTopic\",\"eventType\":\"PaymentCaptured\",\"invoice\":{\"id\":\"qXMiygTqb2\",\"shopID\":1,\"createdAt\":\"2017-05-16T13:49:32.753723Z\",\"status\":\"unpaid\",\"reason\":null,\"dueDate\":\"2017-05-16T13:59:32Z\",\"amount\":100000,\"currency\":\"RUB\",\"metadata\":{\"type\":\"application/json\",\"data\":\"eyJpbnZvaWNlX2R1bW15X2NvbnRleHQiOiJ0ZXN0X3ZhbHVlIn0=\"},\"product\":\"test_product\",\"description\":\"test_invoice_description\"},\"payment\":{\"id\":\"1\",\"createdAt\":\"2017-05-16T13:49:33.182195Z\",\"status\":\"captured\",\"error\":null,\"amount\":100000,\"currency\":\"RUB\",\"paymentToolToken\":\"5Gz2nhE1eleFGBAcGe9SrA\",\"paymentSession\":\"2nTYVgk6h85O7vIVV9j4pA\",\"contactInfo\":{\"email\":\"bla@bla.ru\",\"phoneNumber\":null},\"ip\":\"10.100.2.1\",\"fingerprint\":\"test fingerprint\"}}";
|
||||
String sign = asymSigner.sign(data, keyPair.getPrivKey());
|
||||
byte[] sigBytes = Base64.getDecoder().decode(sign);
|
||||
byte[] sigBytes = Base64.getUrlDecoder().decode(sign);
|
||||
|
||||
byte[] publicBytes = Base64.getDecoder().decode(keyPair.getPublKey());
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
|
||||
|
Loading…
Reference in New Issue
Block a user