签名验证 -凯发k8国际|首页

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 -*-
# 凯发k8国际 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版:

// 凯发k8国际 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 originparams,
                                     string securekey,
                                     long querytime) {
        // 使用签名时间,sk和用户原始请求来生成签名,保证唯一性。
        // 注意三个字符串拼接先后顺序是签名时间,原始请求,私钥
        arraylist list = new arraylist();
        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 queryhash) {
        string[] keys = queryhash.keyset().toarray(new string[0]);
        arrays.sort(keys);
        arraylist params = new arraylist();
        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 originparams = new hashmap();
        // 用户的原始请求
        originparams.put("query", "*");
        // 用户请求签名的时间
        long querytime = new long(new date().gettime());
        // 计算用户签名
        string sign = computesign(originparams, securekey, querytime);
        // 为了验签所有api请求都需要额外增加的参数
        hashmap additionalparams = new hashmap();
        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());
            }
        }
    }
}