API调用-加密身份认证

加密身份认证适用于API共享后调用的场景,具备更高的安全性。调用时需使用API基础信息以及已获得API调用权限的应用相关信息,通过加密算法计算出的签名,将会作为调用时的身份认证信息。本文为您介绍如何调用加密身份认证的API。

背景信息

鉴权说明:为了确保API的使用安全可控,调用方需要在HTTP请求的Header中根据当次请求的具体情况,计算并添加以下参数,数据资源平台根据添加的参数进行校验,校验失败的将被视为非法请求。

参数名称

说明

Date

请求发出时间,GMT格式的日期格式。

signature

当次请求计算产出的签名信息。

Content-MD5

请求内容数据的MD5值。

可为空,只有在请求存在Body且Body为非Form形式时才计算Content-MD5头。

前提条件

目标API的调用认证方式包含加密身份认证

调用示例

使用SDK计算签名(推荐)

针对Java应用提供了的Dbus SDK,Dbus已经将加密身份认证模式所需的签名的计算方法封装在SDK中,以供调用方的Java应用集成,引入SDK后可以直接通过SDK中的方法发起调用请求,无需再手动计算认证签名,方便快捷。

  1. 下载Dbus SDK到本地,如果是Windows环境,在cmd窗口下运行以下命令将签名SDK安装到本地仓库。

    说明

    您需要安装Maven环境变量。

    mvn install:install-file -DgroupId=com.aliyun.dataq.service.dbus -DartifactId=dbus-sdk -Dversion=1.0.3-SNAPSHOT -Dpackaging=jar -Dfile=${安装包所在的本机路径}/dbus-sdk-1.0.3.jar
  2. 在Maven项目的pom.xml文件中加入依赖项。

    <dependencies>
        <dependency>
            <groupId>com.aliyun.dataq.service.dbus</groupId>
            <artifactId>dbus-sdk</artifactId>
            <version>1.0.3-SNAPSHOT</version>
        </dependency>
    </dependencies>
  3. Java应用调用Demo如下,目标API的endpoint、path、AppCode、AppKey以及AppSecret获取方式,请参见获取API调用信息

    说明

    API的访问路径由endpoint和path组成。

    import com.alibaba.cosmo.remote.httpclient.HttpMethod;
    import com.alibaba.fastjson.JSONObject;
    import com.aliyun.dataq.service.dbus.signature.DaasClient;
    import com.aliyun.dataq.service.dbus.signature.DataQService;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.mime.MultipartEntityBuilder;
    import java.io.File;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class Demo {
        public static void main(String[] args) {
            //目标API的地址
            String endpoint = "https://dataq.aliyuncs.com";
            //目标API的路径
            String path = "/yourworkspaceCode/yourAppCode/APIPath";
            //已获得调用授权或者自有API创建时选择的应用Code、AppKey及AppSecret
            String appCode = "yourAppCode";
            String appKey = "yourAppKey";
            String appSecret = "yourAppSecret";
       
            DataQService.Builder builder = new DataQService.Builder();
            builder.setPath(path);
            builder.setAppKey(appKey);
            builder.setAppSecret(appSecret);
            builder.setModuleName(appCode);
    
            //添加请求Header参数
            Map<String, Object> headerMap = new HashMap<>();
            headerMap.put("Accept", "application/json");
            headerMap.put("Content-Type", "application/json");
            headerMap.put("h1", "");
            headerMap.put("h2", "");
            builder.addHeader(headerMap);
    
            //添加QueryParam参数
            Map<String, Object> queryParam = new HashMap<>();
            queryParam.put("p1", "");
            queryParam.put("p2", "");
            builder.addQueryParam(queryParam);
    
            //添加RequestBody参数
            Map<String, Object> map = new HashMap<>();
            map.put("b1", "");
            List<String> values = Arrays.asList("v1");
            map.put("b2", values);
            builder.setRequestBody(new JSONObject(map));
    
            //文件上传
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .addTextBody("k1","v1")
                    .addTextBody("k2","v2")
                    .addBinaryBody("file", new File("/demo/2602780.txt"),  ContentType.APPLICATION_OCTET_STREAM, "2602780.txt");
            builder.setMultipartEntityBuilder(multipartEntityBuilder);
    
            //设置请求方法
            DataQService dataQService = builder.setHttpMethod(HttpMethod.GET).build();
    
            //初始化请求客户端
            DaasClient client = new DaasClient(endpoint);
    
            //发送请求
            Object httpResponse = client.invoke(dataQService, 1000000, 30);
            System.out.println(httpResponse);
        }
    }

手动计算签名

当调用方无法直接使用SDK,或需通过其他方法发起调用请求时,数据资源平台提供以下手动计算签名的方式。

  1. 不同请求方法签名串组成逻辑。

    1. Content计算逻辑如下。

      # get
      ${method}\n${path}\n${date}
      # 非get
      ${method}\n${path}\n${date}\n${bodyMd5}
      说明

      path中如果包含特殊字符需要做Encode。

    2. 对计算出的Content内容,以AppSecret为密钥做HmacSHA1加密后,对密文做base64编码输出。AppSecret获取方式请参见获取API调用信息

  2. Java、Javascript两种应用签名计算示例如下。

    • Java应用签名计算。

      import org.apache.commons.codec.binary.Base64;
      import org.apache.commons.codec.binary.Hex;
      import org.apache.commons.lang3.StringUtils;
      import org.apache.commons.lang3.time.FastDateFormat;
      import javax.crypto.Mac;
      import javax.crypto.spec.SecretKeySpec;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      import java.net.URLDecoder;
      import java.security.MessageDigest;
      import java.security.NoSuchAlgorithmException;
      import java.text.ParseException;
      import java.util.Arrays;
      import java.util.Date;
      import java.util.Locale;
      import java.util.TimeZone;
      
      public abstract class SignUtils {
      
          public static final String EMPTY_STRING_MD5;
      
          private static FastDateFormat DATE_FORMAT;
      
          static {
              DATE_FORMAT = FastDateFormat
                      .getInstance("E, dd MMM yyyy HH:mm:ss z", TimeZone.getTimeZone("GMT"), Locale.UK);
              try {
                  MessageDigest md = MessageDigest.getInstance("md5");
                  md.update("".getBytes("UTF-8"));
                  EMPTY_STRING_MD5 = Base64.encodeBase64String(md.digest());
              } catch (Exception ex) {
                  throw new IllegalArgumentException(ex);
              }
          }
      
          public static Date parseGmtDate(String date) throws ParseException {
              return DATE_FORMAT.parse(date);
          }
      
          public static String formatGmtDate(Date date) {
              return DATE_FORMAT.format(date);
          }
      
          public static String toMd5(InputStream stream) throws Exception {
              return toMd5(stream, false);
          }
      
          public static String toMd5(InputStream stream, boolean parseEmptyStream) throws Exception {
              try {
                  boolean nocontent = true;
                  MessageDigest md = MessageDigest.getInstance("md5");
      
                  int len = -1;
                  byte[] buf = new byte[1024];
      
                  while ((len = stream.read(buf)) != -1) {
                      nocontent = false;
                      md.update(buf, 0, len);
                  }
      
                  return nocontent ?
                          (parseEmptyStream ? EMPTY_STRING_MD5 : "") :
                          Base64.encodeBase64String(md.digest());
              } finally {
                  stream.close();
              }
          }
      
          public static String toMd5(byte[] content) throws Exception {
              if (content == null || content.length == 0) {
                  return null;
              }
              return Base64.encodeBase64String(MessageDigest.getInstance("md5").digest(content));
          }
      
          public static String toMd5(String content) throws Exception {
              return toMd5(content.getBytes("UTF-8"));
          }
      
          public static String toHmacSHA1(String data, String AppSecret) throws Exception {
              String algorithm = "HmacSHA1";
              SecretKeySpec signingKey = new SecretKeySpec(AppSecret.getBytes("UTF-8"), algorithm);
              Mac mac = Mac.getInstance(algorithm);
              mac.init(signingKey);
              byte[] rawHmac = mac.doFinal(data.getBytes("UTF-8"));
              return Base64.encodeBase64String(rawHmac);
          }
      
          public static String sign4Base(String param, String salt) throws Exception {
              if (StringUtils.isEmpty(param) || StringUtils.isEmpty(salt)) {
                  return null;
              }
      
              String query = URLDecoder.decode(param, "UTF-8");
      
              String[] pStr = query.trim().split("&");
              Arrays.sort(pStr);
      
              StringBuilder buf = new StringBuilder(salt).append(':');
              for (int i = 0; i < pStr.length; ++i) {
                  buf.append(pStr[i]);
                  buf.append("&");
              }
              buf.setCharAt(buf.length() - 1, ':');
              buf.append(salt);
      
              MessageDigest md = MessageDigest.getInstance("SHA-256");
              byte[] bytes = md.digest(buf.toString().getBytes("UTF-8"));
      
              return Hex.encodeHexString(bytes).toLowerCase();
          }
      
          private static String signedString(String method, String url, String date, String contentMd5) {
              HttpMethod hm = HttpMethod.valueOf(method.toUpperCase());
              if (hm == null) {
                  throw new IllegalArgumentException("Unknow Http Method: " + method);
              }
      
              if (hm.needBody && contentMd5 == null) {
                  contentMd5 = "";
              }
              if (contentMd5 == null) {
                  StringBuilder buf = new StringBuilder(method.length() + url.length() + date.length());
                  return buf.append(method).append('\n').append(url).append('\n').append(date).toString();
              }
              StringBuilder buf = new StringBuilder(
                      method.length() + url.length() + date.length() + contentMd5.length());
              return buf.append(method).append('\n').append(url).append('\n').append(date).append('\n')
                      .append(contentMd5).toString();
          }
      
          private static String doSign(String module, String appKey, String appSecret, String signedString)
                  throws Exception {
              String signature = toHmacSHA1(signedString, appSecret);
              StringBuilder buf = new StringBuilder(module.length() + appKey.length() + signature.length() + 2);
              return buf.append(module).append(' ').append(appKey).append(':').append(signature).toString();
          }
      
          public static String originalSign4Dtboost(String appSecret, String method, String url, String gmtDate)
                  throws Exception {
              return originalSign4Dtboost(appSecret, method, url, gmtDate, null);
          }
      
          public static String originalSign4Dtboost(String appSecret, String method, String url, String gmtDate,
                                                    String contentMd5) throws Exception {
              String signedString = signedString(method, url, gmtDate, contentMd5);
              return toHmacSHA1(signedString, appSecret);
          }
      
          public static String sign4Dtboost(String module, String appKey, String appSecret, String method,
                                            String url, String gmtDate) throws Exception {
              return sign4Dtboost(module, appKey, appSecret, method, url, gmtDate, null);
          }
      
          public static String sign4Dtboost(String module, String appKey, String appSecret, String method,
                                            String url, String gmtDate, String contentMd5) throws Exception {
              String signedString = signedString(method, url, gmtDate, contentMd5);
              return doSign(module, appKey, appSecret, signedString);
          }
      
          public static String[] spliteSign4Dtboost(String signature) {
              int moduleIndex = signature.indexOf(' ');
              if ((moduleIndex < 1) || (moduleIndex + 3) == signature.length()) {
                  return null;
              }
              int accessIdIndex = signature.indexOf(':', moduleIndex);
              if ((accessIdIndex < 2) || (accessIdIndex + 1) == signature.length()) {
                  return null;
              }
              return new String[] {signature.substring(0, moduleIndex),
                      signature.substring(moduleIndex + 1, accessIdIndex), signature.substring(accessIdIndex + 1)};
          }
      
          private static String signedString(String method, String accept, String gmtDate, String url,
                                             String contentType, String contentMd5) {
              if (accept == null) {
                  accept = "";
              }
              if (contentType == null) {
                  contentType = "";
              }
              if (contentMd5 == null) {
                  contentMd5 = "";
              }
              method = method.toUpperCase();
      
              StringBuilder buf = new StringBuilder(
                      method.length() + accept.length() + gmtDate.length() + url.length() + contentType.length()
                              + contentMd5.length());
      
              return buf.append(method).append('\n').append(accept).append('\n').append(contentMd5).append('\n')
                      .append(contentType).append('\n').append(gmtDate).append('\n').append(url).toString();
          }
      
          public static String originalSign4Dataplus(String appSecret, String method, String accept, String gmtDate,
                                                     String url) throws Exception {
              return originalSign4Dataplus(appSecret, method, accept, gmtDate, url, null, null);
          }
      
          public static String originalSign4Dataplus(String appSecret, String method, String accept, String gmtDate,
                                                     String url, String contentType, String contentMd5) throws Exception {
              String signedString = signedString(method, accept, gmtDate, url, contentType, contentMd5);
              return toHmacSHA1(signedString, appSecret);
          }
      
          @Deprecated public static String sign4Datapuls(String module, String appKey, String appSecret,
                                                         String method, String accept, String gmtDate, String url) throws Exception {
              return sign4Dataplus(module, appKey, appSecret, method, accept, gmtDate, url);
          }
      
          public static String sign4Dataplus(String module, String appKey, String appSecret, String method,
                                             String accept, String gmtDate, String url) throws Exception {
              return sign4Dataplus(module, appKey, appSecret, method, accept, gmtDate, url, null, null);
          }
      
          @Deprecated public static String sign4Datapuls(String module, String appKey, String appSecret,
                                                         String method, String accept, String gmtDate, String url, String contentType, String contentMd5)
                  throws Exception {
              return sign4Datapuls(module, appKey, appSecret, method, accept, gmtDate, url, contentType,
                      contentMd5);
          }
      
          public static String sign4Dataplus(String module, String appKey, String appSecret, String method,
                                             String accept, String gmtDate, String url, String contentType, String contentMd5)
                  throws Exception {
              String signedString = signedString(method, accept, gmtDate, url, contentType, contentMd5);
              return doSign(module, appKey, appSecret, signedString);
          }
      
          public static String[] spliteSign4Dataplus(String signature) {
              return spliteSign4Dtboost(signature);
          }
      
          enum HttpMethod {
              /**
               * HTTP METHOD
               */
              GET(false), POST(true), PUT(true), DELETE(false), PATCH(true), HEAD(false), OPTIONS(false), TRACE(
                      false);
      
              private final boolean needBody;
      
              private HttpMethod(boolean needBody) {
                  this.needBody = needBody;
              }
          }
      
          public static final class Md5CalculatedOutputStream extends OutputStream {
      
              private boolean nocontent = true;
      
              private final MessageDigest md;
              private final boolean parseEmptyStream;
      
              public Md5CalculatedOutputStream() throws NoSuchAlgorithmException {
                  this(false);
              }
      
              public Md5CalculatedOutputStream(boolean parseEmptyStream) throws NoSuchAlgorithmException {
                  md = MessageDigest.getInstance("md5");
                  this.parseEmptyStream = parseEmptyStream;
              }
      
              @Override public void write(int b) throws IOException {
                  if (nocontent) {
                      nocontent = false;
                  }
                  md.update((byte) b);
              }
      
              @Override public void write(byte b[]) throws IOException {
                  if (b == null) {
                      throw new NullPointerException();
                  } else if (b.length != 0) {
                      if (nocontent) {
                          nocontent = false;
                      }
                      md.update(b);
                  }
      
              }
      
              @Override public void write(byte b[], int off, int len) throws IOException {
                  if (b == null) {
                      throw new NullPointerException();
                  } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len)
                          < 0)) {
                      throw new IndexOutOfBoundsException();
                  } else if (len != 0) {
                      if (nocontent) {
                          nocontent = false;
                      }
                      md.update(b, off, len);
                  }
              }
      
              public String toMd5() {
                  if (nocontent) {
                      return parseEmptyStream ? EMPTY_STRING_MD5 : "";
                  }
                  return Base64.encodeBase64String(md.digest());
              }
          }
      }
    • Js应用签名计算。

      AppKey、AppSecret的获取方式请参见获取API调用信息

      function sign(url, method, headers, appKey, appSecret, body) {
            const appSecret = appSecret;
            const md5 = function (buffer) {
              return crypto.createHash('md5').update(buffer).digest('base64');
            };
            const sha1 = function (stringToSign, appSecret) {
              return crypto.createHmac('sha1', AppSecret).update(stringToSign).digest().toString('base64');
            };
            body = body || {};
            let bodymd5 = '';
            if (body && _.size(body) && ['POST', 'PUT', 'PATCH'].indexOf(method.toUpperCase()) >= 0) {
              bodymd5 = crypto.createHash('md5').update(Buffer.from(JSON.stringify(body))).digest('base64');
            }
            let stringToSign = method + '\n' + Url.parse(url).path + '\n' + headers.date;
            if (bodymd5) {
              stringToSign = stringToSign + '\n' + bodymd5;
            }
            let signature = sha1(stringToSign, appSecret);
            let authHeader = `common-user-ak-v1 ${appKey}:${signature}`;
            return authHeader;
        }