contact@yottabyte.cn
400-085-0159
调查问卷
J

API

概述

日志易RESTful API提供对日志易系统的调用接口。当前仅作为日志易企业版的一部分发布,便于用户以灵活的方式集成日志易系统。

签名验证

概述

访问日志易RESTful API服务需要进行签名验证。签名验证的目的是:

  1. 验证用户身份:只有拥有表明用户身份的key,用户才能够访问API服务。
  2. 保护网络传输过程中用户请求不被篡改:API会验证用户使用key为每次请求计算出的签名,以防止在网络传输过程中用户的请求被篡改。
  3. 防止重复请求攻击(Replay attack):当API使用者的HTTP请求记录被窃取(如:访问API的日志文件泄露),导致窃取者可以重复发送此的请求,从API服务窃取对应的查询结果。

通过给用户分发access_key、secure_key,API服务提供了基于MD5的签名验证机制。其中针对重复请求攻击,API服务承诺会接受当前时间一分钟之内的请求,对于签名生成时间到API服务接收时间之间间隔超过一分钟的请求则直接拒绝。

获取签名需要的key

请从API服务提供者处获取一对access_key和secure_key。key的形式为长度为32的字符串。举例如下:

使用签名

签名计算过程:

  1. 将原始请求构造成Hash形式origin_params,hash的key与value均为字符串。
  2. 将origin_params的key按照字母顺序排序后,key与value之间用=进行字符串链接,每对key之间用&进行字符串链接,构造一个用来生成签名的字符串sorted_query_str
  3. 将以毫秒计的当前Unix时间戳query_time和从API提供者处获得的secure_key跟sorted_query_str进行字符串链接获得字符串进行MD5计算,其结果截取前32个字符即为本次请求的sign

在原始请求之上,添加 { ‘qt’: query_time, ‘ak’: access_key, ‘sign’: sign } 作为额外参数即可使用签名访问API服务。

下面的python样例程序演示了如何使用签名:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 Yottabyte
import time
import hashlib
import urllib2
import urllib

_access_key = "535479d978359bf4975acf7e7f3f0147"
_secure_key = "e31429454b845ae95ece2cbeae06a3a6"

# @brief: API签名计算
# @param: origin_params 是用户原始的请求hash
# @param: secure_key是分发给用户的secure_key
# @param: query_time是签名的时间,为以毫秒计的Unix时间戳
# @returns: 长度为32的API签名结果
def _compute_sign(origin_params, secure_key, query_time):
  sign_arr = []

  # 使用签名时间,sk和用户原始请求来生成签名,保证唯一性。
  # 注意三个字符串拼接先后顺序是签名时间,原始请求,私钥
  sign_arr.append(str(query_time))
  sign_arr.append(_sorted_query_str(origin_params))
  sign_arr.append(secure_key)

  return _md5("".join(sign_arr))


# @brief: 通过md5摘要生成算法计算签名
# @param: i_str是待计算的字符串
# @returns: md5的结果截取前32位
def _md5(i_str):
  h = hashlib.md5()
  h.update(i_str)
  return h.hexdigest()[0:32]


# @brief: 将用户的原始请求按照key排序后拼接为一个字符串
# @param: query_hash 用户原始的请求参数,可以为空{}
# @returns: 原始请求对应的唯一字符串
def _sorted_query_str(query_hash):
  return "&".join([ k+"="+query_hash[k] for k in sorted(query_hash.keys()) ])

if __name__ == '__main__':
  #用户的原始请求
  origin_params = {'query': '*'}
  #用户请求签名的时间
  qtime = int(time.time() * 1000)
  #计算用户签名
  sign = _compute_sign(origin_params, _secure_key, qtime)

  # 为了验签所有API请求都需要额外增加的参数
  additional_params = {
    'qt': qtime,
    'sign' : sign,
    'ak': _access_key,
  }

  # 将用户原始参数和额外参数合并
  req_params = dict(origin_params.items() + additional_params.items())

  # 拼接query
  req_url = 'http://yottaapi-test:8190/v0/search/timeline/?' + urllib.urlencode(req_params)

  # 发起请求,获取结果
  print(urllib2.urlopen(req_url).read())

java版:

// Copyright 2014 Yottabyte
package cn.yottabyte.api;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;


public class Main {

    public static String accessKey = "319fcdc428de7204eb2df6a0a0c95a76";
    public static String secureKey = "9cbca466e9621976d52013f4eb375356";

    // @brief: API签名计算
    // @param: originParams 是用户原始的请求hash
    // @param: secureKey是分发给用户的secure_key
    // @param: queryTime是签名的时间,为以毫秒计的Unix时间戳
    // @return: 长度为32的API签名结果
    public static String computeSign(HashMap<String, String> originParams,
                                     String secureKey,
                                     Long queryTime) {
        // 使用签名时间,sk和用户原始请求来生成签名,保证唯一性。
        // 注意三个字符串拼接先后顺序是签名时间,原始请求,私钥
        ArrayList<String> list = new ArrayList<String>();
        list.add(queryTime.toString());
        list.add(sortedQueryStr(originParams));
        list.add(secureKey);
        return md5(StringUtils.join(list.toArray(new String[0]), ""));
    }

    // @brief: 通过md5摘要生成算法计算签名
    // @param: sourceStr是待计算的字符串
    // @returns: md5的结果截取前32位
    public static String md5(String sourceStr) {
        String result;
        try {
            byte[] bytesOfMessage = sourceStr.getBytes("UTF-8");
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] thedigest = md.digest(bytesOfMessage);

            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < thedigest.length; offset++) {
                i = thedigest[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            result = buf.toString();
        } catch (Exception e) {
            System.err.println("Exception in md5.");
            result = "";
        }
        return result;
    }

    // @brief: 将用户的原始请求按照key排序后拼接为一个字符串
    // @param: queryHash 用户原始的请求参数,可以为空{}
    // @returns: 原始请求对应的唯一字符串
    public static String sortedQueryStr(HashMap<String, String> queryHash) {
        String[] keys = queryHash.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        ArrayList<String> params = new ArrayList<String>();
        for (int i = 0; i < keys.length; i++) {
            String k = keys[i];
            String v = queryHash.get(k);
            params.add(k + "=" + v);
        }
        return StringUtils.join(params.toArray(new String[0]), "&");
    }

    public static void main(String[] args) {
        HashMap<String, String> originParams = new HashMap<String, String>();
        // 用户的原始请求
        originParams.put("query", "*");
        // 用户请求签名的时间
        Long queryTime = new Long(new Date().getTime());
        // 计算用户签名
        String sign = computeSign(originParams, secureKey, queryTime);

        // 为了验签所有API请求都需要额外增加的参数
        HashMap<String, String> additionalParams = new HashMap<String, String>();
        additionalParams.put("qt", queryTime.toString());
        additionalParams.put("sign", sign);
        additionalParams.put("ak", accessKey);

        // 拼接query
        URIBuilder uriBuilder = new URIBuilder();
        uriBuilder.setScheme("http");
        uriBuilder.setHost("yottaapi.test");
        uriBuilder.setPort(8190);
        uriBuilder.setPath("/v0/search/timeline/");
        String[] originKeys = originParams.keySet().toArray(new String[0]);
        for (int i = 0; i < originKeys.length; i++) {
            uriBuilder.addParameter(originKeys[i], originParams.get(originKeys[i]));
        }
        String[] additionalKeys = additionalParams.keySet().toArray(new String[0]);
        for (int i = 0; i < additionalKeys.length; i++) {
            uriBuilder.addParameter(additionalKeys[i], additionalParams.get(additionalKeys[i]));
        }

        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            // 打印GET方法的uri
            String uri = uriBuilder.build().toString();
            System.out.println(uri);
            // 发起请求
            HttpGet httpGet = new HttpGet(uri);
            CloseableHttpResponse response = httpclient.execute(httpGet);
            try {
                System.out.println(response.getStatusLine());
                HttpEntity entity = response.getEntity();
                // 获得结果
                System.out.println(EntityUtils.toString(entity));
                EntityUtils.consume(entity);
            } catch(Exception e) {
                System.err.println("Handle result exception!" + e.toString());
            } finally {
                response.close();
            }
        } catch(Exception e) {
            System.err.println("Request Exception!" + e.toString());
        } finally {
            try {
                httpclient.close();
            } catch(Exception e) {
                System.out.println("Close client Exception!" + e.toString());
            }
        }
    }
}