Чтобы взаимодействовать с API сервиса, необходимо аутентифицироваться в системе.
Для аутентификации запросов API использует специальные ключи доступа — токены, передаваемые в заголовке запроса. Использование токена позволяет избавиться от передачи имени пользователя и пароля в открытом виде и обеспечивает их защиту при работе с сервисом. Пароль пользователя известен только клиенту.
Формирование токена выполняет клиент с помощью хеширования данных учётной записи и меток времени.
Каждый запрос к API должен содержать HTTP-заголовок Authorization
со значением AR-REST <token>
.
Токен имеет формат base64(user:stamp:age:salted_hash)
, где:
user
— UID пользователя в формате name@domain
;stamp:age
— метка времени:
stamp
— дата начала действия токена с точностью до секунды (UTC + 0 в Unix-time);age
— время жизни токена в секундах;salted_hash
— солёный хеш пароля base64(md5(salt:pass_hash))
:
pass_hash
= base64(md5(pass))
— хешированный пароль pass
пользователя;salt
— метка времени stamp:age
.{primary.fa-terminal} Для отладки формирования токена используйте команду Swagger
/dev/token
.Не передавайте в Swagger или другие внешние сервисы данные своей учётной записи в незашифрованном виде: они могут быть перехвачены и прочитаны злоумышленниками.
user = test_user@test_domain
pass = 123
stamp = 1483634723 (2017-01-05T16:45:23.000Z)
age = 999999999
pass_hash
:
md5(pass)
ICy5YqxZB1uWSwcVLSNLcA==
{primary.fa-info} Для кодирования в Base64 следует использовать бинарное представление хеша (сырой массив из 16 байт).
Онлайн-сервисы хеширования обычно возвращают ответ в виде строки, содержащей шестнадцатеричное число. При конвертации такого числа в Base64 убедитесь, что тип исходных данных — шестнадцатеричное число, а не текст.
Проверить корректность формирования
pass_hash
можно, используя сервисы Online-Convert или Cryptii.
salted_hash
:
stamp:age
добавьте pass_hash
: 1483634723:999999999:ICy5YqxZB1uWSwcVLSNLcA==
md5(pass_hash)
и сконвертируйте его в Base64: 3wg82EuTwec29/OvQ7myyA==
token
:
user:stamp:age:salted_hash
: test_user@test_domain:1483634723:999999999:3wg82EuTwec29/OvQ7myyA==
dGVzdF91c2VyQHRlc3RfZG9tYWluOjE0ODM2MzQ3MjM6OTk5OTk5OTk5OjN3ZzgyRXVUd2VjMjkvT3ZRN215eUE9PQ==
Используйте сгенерированный токен в HTTP-заголовке Authorization
:
Authorization: AR-REST dGVzdF91c2VyQHRlc3RfZG9tYWluOjE0ODM2MzQ3MjM6OTk5OTk5OTk5OjN3ZzgyRXVUd2VjMjkvT3ZRN215eUE9PQ==
Время жизни токена — период, в течение которого он является действительным. Ограниченный срок жизни позволяет более надёжно защитить отправляемые запросы. Даже в случае кражи токена злоумышленник не сможет выполнить запросы с ним после окончания его срока действия. Чем меньше время жизни токена, тем более безопасным он является.
{primary.fa-info} Мы рекомендуем использовать минимальное время жизни токена, но не менее 30 секунд. Такой период снижает риск потери запроса из-за задержек сети или рассинхронизации времени сервера и клиента.
В качестве даты начала действия токена можно использовать текущее время.
Использование токенов с длительным сроком жизни и отказ от генерации новых токенов для каждого запроса не является безопасным и увеличивает вероятность компрометации данных.
package main
import (
"crypto/md5"
"encoding/base64"
"fmt"
"time"
)
func generateToken(uid, password string, age time.Duration) (string, error) {
hasher := md5.New()
if _, err := hasher.Write([]byte(password)); err != nil {
return "", err
}
passHash := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
hasher.Reset()
stamp := time.Now().Unix()
ttl := int(age.Seconds())
salted := fmt.Sprintf("%d:%d:%s", stamp, ttl, passHash)
if _, err := hasher.Write([]byte(salted)); err != nil {
return "", err
}
saltedPassHash := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
tokenRaw := fmt.Sprintf("%s:%d:%d:%s", uid, stamp, ttl, saltedPassHash)
return base64.StdEncoding.EncodeToString([]byte(tokenRaw)), nil
}
func main() {
token, err := generateToken("test@test", "SomeStrongPa$$w0rd", 24 * time.Hour)
if err != nil {
panic(err)
}
fmt.Println(token)
}
from base64 import b64encode
from hashlib import md5
from time import time
def generate_token(user: str, password: str, age: int = 60 * 60 * 24) -> str:
timestamp = int(time())
password_hash = b64encode(md5(password.encode()).digest()).decode()
hash_with_salt = f"{timestamp}:{age}:{password_hash}"
salted_hash_b64 = b64encode(md5(hash_with_salt.encode()).digest()).decode()
token = f"{user}:{timestamp}:{age}:{salted_hash_b64}"
token_b64 = b64encode(token.encode()).decode()
return token_b64
if __name__ == '__main__':
api_token = generate_token(
user='test_user@test_domain',
password='123',
age=999999999
)
print(api_token)
$user = $request->get('uid');
$pass = $request->get('password');
$stamp = time();
$age = 60*60*24;
$passHash = base64_encode(md5($pass, true));
$saltedHash = base64_encode(md5($stamp.':'.$age.':'.$passHash, true));
$token = base64_encode(implode(':', [$user, $stamp, $age, $saltedHash]));
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
public void genTokenExamle() throws ParseException, NoSuchAlgorithmException {
String user = "test@test";
String pass = "123";
long stamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")).parse("2017-01-05T16:45:23Z").getTime() / 1000;
long age = 999999999;
MessageDigest md = MessageDigest.getInstance("MD5");
String pass_hash = Base64.getEncoder().encodeToString(md.digest(pass.getBytes()));
String salt = stamp + ":" + age;
String salt_hash = Base64.getEncoder().encodeToString(md.digest((salt + ":" + pass_hash).getBytes()));
String token = Base64.getEncoder().encodeToString((user + ":" + stamp + ":" + age + ":" + salt_hash).getBytes());
String header = "AR-REST " + token;
System.out.println(header);
}
Запросы, использующие недействительные токены (сформированные некорректно или использующие неверные данные учётной записи) будут обработаны с ошибкой. API возвращает объект ошибки Security
и её описание. Все возможные ошибки обработки запросов указаны в разделе Ошибки обработки запросов.