加密身份认证适用于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中的方法发起调用请求,无需再手动计算认证签名,方便快捷。
下载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
在Maven项目的pom.xml文件中加入依赖项。
<dependencies> <dependency> <groupId>com.aliyun.dataq.service.dbus</groupId> <artifactId>dbus-sdk</artifactId> <version>1.0.3-SNAPSHOT</version> </dependency> </dependencies>
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,或需通过其他方法发起调用请求时,数据资源平台提供以下手动计算签名的方式。
不同请求方法签名串组成逻辑。
Content计算逻辑如下。
# get ${method}\n${path}\n${date} # 非get ${method}\n${path}\n${date}\n${bodyMd5}
说明path中如果包含特殊字符需要做Encode。
对计算出的Content内容,以AppSecret为密钥做HmacSHA1加密后,对密文做base64编码输出。AppSecret获取方式请参见获取API调用信息。
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; }