C2 监控/系统外呼/话单/知识库类接口鉴权方式
AK/SK获取方式
AK/SK认证
AK/SK认证就是使用AK/SK对请求进行签名,在请求时将签名信息添加到消息头,从而通过身份认证。
-
AK(Access Key ID):访问密钥ID。与私有访问密钥关联的唯一标识符;访问密钥ID和私有访问密钥一起使用,对请求进行加密签名。
-
SK(Secret Access Key):与访问密钥ID结合使用的密钥,对请求进行加密签名,可标识发送方,并防止请求被修改。
使用AK/SK认证时,您可以基于签名算法使用AK/SK对请求进行签名。
私有AK、SK获取方式
- 集成环境:请联系运营人员获取。
接口鉴权如果通过 ,则https响应码为200;如果不通过,则https的响应码为401。
鉴权算法
下面以CC-CMS接口为例介绍鉴权算法,其余容器化网元CC-iKBS、虚拟化网元CC-FS、CC-iSales的鉴权算法可直接参考。
所有接口服务器端都会对客户端的请求数据做鉴权签名验证,鉴权流程如下:
调用者生成Authorization的生成规则内容如下:
各内容的算法和规则
- CmsParameters
该参数类为CC-CMS二次开发demo中的参数汇总,在接下来的描述、调用中,会直接取该类中的参数。使用者只需统一修改该参数类,即可完成相应的鉴权配置。
以下是demo中基本的参数样例,调用者可根据具体情况,增加相应的参数,以方便后续的项目管理:
/** * CC-CMS接口请求参数类定义 */ public class CmsParameters { //以下信息需要根据实际信息替换 /** * host访问地址 */ public static final String host = "10.22.26.181:28080"; public static final String url = "https://" + host; /** * 开发者ID,即AK */ public static final String accessKey= "globalaktest"; /** * 开发者秘钥(未加密),即SK */ public static final String secretKey = "1q************20"; }
- SignInfo
该类主要包含一些固定HEAD的定义,以及一些用于构造CanonicalRequest和Signature的基本函数。
package com.huawei.client.rest.v2.demo.sign; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; @Setter @Getter public class SignInfo { public static final String HEAD_AUTHORIZATION = "authorization"; public static final String HEAD_HOST = "host"; public static final String HEAD_CONTENT_LENGTH = "Content-Length"; public static final String HEAD_CONTENT_TYPE = "Content-Type"; public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; // 默认值,不用设置 private String authVersion = "auth-v2"; private String httpMethod; private String uri; // 当前服务侧暂无GET支持 private Map<String, String> queryParameters; private Map<String, String> signedHeaders; private String payload; private String accessKey; private String secretKey; private Date timestamp; public String authString() throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { String authStringPrefix = this.authStringPrefix(); String signingKey = SignerUtils.sha256Hex(this.getSecretKey(), authStringPrefix); String canonicalRequest = this.canonicalRequest(); String signature = SignerUtils.sha256Hex(signingKey, canonicalRequest); String authString = authStringPrefix + '/' + signature; return authString; } public String authStringPrefix() { StringBuilder buffer = new StringBuilder(); buffer.append(this.authVersion); buffer.append('/').append(this.accessKey); buffer.append('/').append(this.formatTimestamp()); buffer.append('/'); this.appendSignedHeaders(buffer); return buffer.toString(); } public String canonicalRequest() { StringBuilder buffer = new StringBuilder(); buffer.append(this.httpMethod).append('\n'); buffer.append(this.uri).append('\n'); if (this.isNotEmpty(this.queryParameters)) { this.appendCanonicalQueryString(buffer); buffer.append('\n'); } this.appendSignedHeaders(buffer); buffer.append('\n'); this.appendCanonicalHeaders(buffer); buffer.append('\n'); if (this.isNotEmpty(this.payload)) { buffer.append(PathUtils.normalize(this.payload)); } return buffer.toString(); } private String appendSignedHeaders(StringBuilder buffer) { int start = buffer.length(); Set<String> headerNames = new TreeSet<>(this.signedHeaders.keySet()); for (String name : headerNames) { buffer.append(name.toLowerCase(Locale.ENGLISH)).append(';'); } buffer.deleteCharAt(buffer.length() - 1); int end = buffer.length(); String signedHeadersStr = buffer.substring(start, end); return signedHeadersStr; } private String appendCanonicalHeaders(StringBuilder buffer) { int start = buffer.length(); Set<String> headers = new TreeSet<>(); for (Map.Entry<String, String> entry : this.signedHeaders.entrySet()) { String header = PathUtils.normalize(entry.getKey()) + ':' + PathUtils.normalize(entry.getValue()); headers.add(header); } for (String header : headers) { buffer.append(header).append('\n'); } buffer.deleteCharAt(buffer.length() - 1); int end = buffer.length(); String canonicalHeadersStr = buffer.substring(start, end); return canonicalHeadersStr; } private void appendCanonicalQueryString(StringBuilder buffer) { // 编码并排序 Set<String> sortedSet = new TreeSet<>(); for (Map.Entry<String, String> e : this.queryParameters.entrySet()) { String uriEncodeKey = PathUtils.normalize(e.getKey()); String uriEncodeValue = this.isNotEmpty(e.getValue()) ? PathUtils.normalize(e.getValue()) : ""; sortedSet.add(uriEncodeKey + "=" + uriEncodeValue); } for (String e : sortedSet) { buffer.append(e).append('&'); } buffer.deleteCharAt(buffer.length() - 1); } private String formatTimestamp() { SimpleDateFormat format = new SimpleDateFormat(SignInfo.TIMESTAMP_FORMAT); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format.format(this.timestamp); } private boolean isNotEmpty(String str) { if ((null == str) || str.isEmpty()) { return false; } return true; } private <K, V> boolean isNotEmpty(Map<K, V> map) { if ((null == map) || map.isEmpty()) { return false; } return true; } }
其中,SignInfo类用到的PathUtils工具类如下:
package com.huawei.client.rest.v2.demo.utils; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.BitSet; public class PathUtils { private static final String CHARSET = "UTF-8"; private static BitSet URI_UNRESERVED_CHARACTERS = new BitSet(); private static String[] PERCENT_ENCODED_STRINGS = new String[256]; static { for (int i = 97; i <= 122; i++) { PathUtils.URI_UNRESERVED_CHARACTERS.set(i); } for (int i = 65; i <= 90; i++) { PathUtils.URI_UNRESERVED_CHARACTERS.set(i); } for (int i = 48; i <= 57; i++) { PathUtils.URI_UNRESERVED_CHARACTERS.set(i); } PathUtils.URI_UNRESERVED_CHARACTERS.set(45); PathUtils.URI_UNRESERVED_CHARACTERS.set(46); PathUtils.URI_UNRESERVED_CHARACTERS.set(95); PathUtils.URI_UNRESERVED_CHARACTERS.set(126); for (int i = 0; i < PathUtils.PERCENT_ENCODED_STRINGS.length; i++) { PathUtils.PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", new Object[] {Integer.valueOf(i)}); } } public static String normalizePath(String path) { return PathUtils.normalize(path).replace("%2F", "/"); } public static String normalize(String value) { try { StringBuilder builder = new StringBuilder(); for (byte b : value.getBytes(PathUtils.CHARSET)) { if (PathUtils.URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) { builder.append((char) b); } else { builder.append(PathUtils.PERCENT_ENCODED_STRINGS[(b & 0xFF)]); } } return builder.toString(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * URL 归一化处理 * * @param url * @return */ public static String normalizeURL(String url) { try { return URLDecoder.decode(new URI(url).normalize().toString(), PathUtils.CHARSET); } catch (URISyntaxException | UnsupportedEncodingException e) { return url; } } public static void main(String[] args) { System.out.println(PathUtils.normalize("123%456")); } }
- SignedHeaders
调用者对HTTPS请求中的Header部分进行选择性编码,也可以自行决定哪些Header参与编码,唯一要求是Host域必须被编码,但headname=“Authorization”的head不能参与编码计算,因为Authorization是最终鉴权head域。
备注:Host的值为对应服务的https地址中的ip:port 。
大多数情况下,我们推荐对以下Header进行编码:
Host="10.22.26.181:28080" Content-Length="22" Content-Type="application/json;charset=UTF-8"
- 内容计算规则:
遍历参与编码的HttpHead里面的Header name
- 把Header name都改为小写,即调用lowerCase()函数;
- 把上面Header name转换后的字符后面追加分割符";",生成一条记录,注意最后一个字段不追加";";
- 把上面所有记录按照字典排序,然后按照顺序连接成一个大字符串。
- 代码实现:
- 构建signedHeaders的Map,填充所需字段信息,然后调用lowerCaseSignedHeaders函数进行字符处理:
// 参与签名鉴权的head字段,host字段是必须的 Map<String, String> signedHeaders = new HashMap<>(); signedHeaders.put(SignInfo.HEAD_HOST, CmsParameters.host); signedHeaders.put(SignInfo.HEAD_CONTENT_LENGTH, String.valueOf(bodyJson.getBytes("UTF-8").length)); signedHeaders.put(SignInfo.HEAD_CONTENT_TYPE, "application/json;charset=UTF-8"); // 鉴权数据接口设置 SignInfo signInfo = new SignInfo(); signInfo.setAccessKey(CmsParameters.accessKey); signInfo.setSecretKey(CmsParameters.secretKey); signInfo.setPayload(bodyJson); signInfo.setTimestamp(new Date()); signInfo.setHttpMethod(HttpProxyHelper.HTTP_METHOD_POST); signInfo.setUri(httpPath); signInfo.setSignedHeaders(lowerCaseSignedHeaders(signedHeaders));
以上函数的完整代码参见:如何调通第一个接口
- 函数:lowerCaseSignedHeaders的具体实现:
/** * 参与计算的signedHeaders进行转换 * @param * @return Map<String, String> */ private Map<String, String> lowerCaseSignedHeaders(Map<String, String> signedHeaders) { if ((null == signedHeaders) || signedHeaders.isEmpty()) { throw new IllegalArgumentException("signedHeaders cann't be null."); } Map<String, String> headers = new HashMap<>(); for (Entry<String, String> e : signedHeaders.entrySet()) { String name = e.getKey(); String value = e.getValue(); headers.put(name.toLowerCase(Locale.ENGLISH), value.trim()); } if (!signedHeaders.containsKey(SignInfo.HEAD_HOST)) { throw new IllegalArgumentException("signedHeaders must has host."); } return headers; }
- 构建signedHeaders的Map,填充所需字段信息,然后调用lowerCaseSignedHeaders函数进行字符处理:
- 具体例子:
例子:参与编码的HttpHead如下: Host="10.22.26.181:28080" Content-Length="22" Content-Type="application/json;charset=UTF-8" 经过上述规则处理后为: SignedHeaders=content-length;content-type;host
- 内容计算规则:
- CanonicalHeaders
其要求计算的编码规则与SignedHeaders一致,但增加了head value的编码。
- 内容计算规则:
遍历参与编码的HttpHead里面的head name
- 把Header name都改为小写,即调用lowerCase()函数;
- 调用NormalizePath函数,对刚才转换后的小写字符串进行格式化;
- 格式化后的字符串+":"+NormalizePath((Header value).trim()),生成一条记录字符串;
- 把上面的记录按照字典排序,进行排序;
- 遍历排序后的记录,中间追加字符串"\n"连接成一个大的字符串:
最后一条记录不追加"\n"。
例子:参与编码的HttpHead如下: Host="10.22.26.181:28080" Content-Length="22" Content-Type="application/json;charset=UTF-8" 经过上述规则处理后CanonicalHeaders为: content-length:22\n content-type:application%2Fjson%3Bcharset%3DUTF-8\n host:10.22.26.181%3A28080
- 内容计算规则:
- CanonicalRequest
- 内容计算规则:
CanonicalRequest = $HttpMethod + "\n" + $HttpURI+ "\n" + $HttpParameters + "\n" + SignedHeaders($HttpHeaders) + "\n" + CanonicalHeaders ($HttpHeaders) + "\n" + NormalizePath($HttpBody)
参数描述:
- $HttpMethod,指HTTPS协议中定义的GET、PUT、POST等请求,必须使用全大写的形式。所涉及的HTTP Method有:GET、POST、PUT、DELETE、HEAD,但当前CC-CMS服务仅支持POST这1种;
- $HttpURI,指接口请求的https URI,比如完整url为https://10.22.26.181:28080/rest/cmsapp/v1/ping那么HttpURI就为/rest/cmsapp/v1/ping,必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”;
- $HttpParameters,指接口请求URI后面的请求参数,比如https://10.22.26.181:28080/rest/cmsapp/v1/ping?id=123&name=test,那么HttpParameters就为id=123&name=test,CC-CMS接口目前没有该参数。
- $HttpBody:是通过HTTPS BODY体提交到服务器端的字符串,该字符串的形式为标准JSON串,具体字段见各接口的定义。
- 代码实现:
参见•SignInfo中的canonicalRequest()函数。
- 内容计算规则:
- 生成认证字符串Authorization
- 内容计算规则:
- 首先生成authStringPrefix和SigningKey
- 关于authStringPrefix和SigningKey的生成规则:
authStringPrefix="auth-v2/{accessKey}/{timestamp}/{SignedHeaders}"; SigningKey = sha256Hex(secretKey, authStringPrefix);
说明:
auth-v2:鉴权版本号,当期版本为固定字符串“auth-v2”;
accessKey:调用者的鉴权ID,即AK;
secretKey:调用者的鉴权密钥,即SK;
timestamp:调用者端生成的UTC时间,时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss.SSS'Z";
- 代码实现:authStringPrefix
生成authStringPrefix的函数在SignInfo类的如下两个函数中实现
authStringPrefix() appendSignedHeaders(StringBuilder buffer)
- 代码实现:SigningKey:
String signingKey = SignerUtils.sha256Hex(this.getSecretKey(), authStringPrefix);
其中的sha256Hex()加密算法:参见如下工具类package com.huawei.client.rest.v2.demo.utils; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** * Rest接口认证摘要算法工具类 */ public class SignerUtils { private static final String CHARSET = "UTF-8"; private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * 摘要算法 * @param key * @param toSigned * @return String * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws UnsupportedEncodingException */ public static String sha256Hex(String key, String toSigned) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getBytes(SignerUtils.CHARSET), "HmacSHA256")); String digit = new String(SignerUtils.encodeHex(mac.doFinal(toSigned.getBytes(SignerUtils.CHARSET)))); return digit; } private static char[] encodeHex(final byte[] data) { final int l = data.length; final char[] out = new char[l << 1]; for (int i = 0, j = 0; i < l; i++) { out[j++] = SignerUtils.DIGITS_LOWER[(0xF0 & data[i]) >>> 4]; out[j++] = SignerUtils.DIGITS_LOWER[0x0F & data[i]]; } return out; } }
- 关于authStringPrefix和SigningKey的生成规则:
- 生成签名Signature
- 签名的生成规则:
Signature = sha256Hex(SigningKey, CanonicalRequest)
- 代码实现:包含加密函数:sha256Hex
参见上述:代码实现SigningKey中的sha256Hex()实现方法。
- 签名的生成规则:
- 认证字符串
- 认证字符串的生成规则:
Authorization:$authStringPrefix/$Signature
- 代码实现:其中,signedHeaders为上述signedHeaders模块中构建的Map。
// 生成签名 String signature = signInfo.authString(); Map<String, String> httpHeads = new HashMap<>(signedHeaders); // 追加到HTTPHEAD,发送到服务器端 httpHeads.put(SignInfo.HEAD_AUTHORIZATION, signature);
- 认证字符串的生成规则:
认证字符串要通过HttpHead的“Authorization”域,随请求发送给CC-CMS服务端。
- 首先生成authStringPrefix和SigningKey
- 内容计算规则:
- 完整例子
- 如何调通第一个接口:
调通一个接口,鉴权是必要的一环。
在该项目中,鉴权入口在于buildSignHttpHead()函数,该函数构造了signedHeaders的Map,并将改Map传入SignInfo中;
SignInfo类中的函数authString() ,揭示了构造认证字符串的主要过程,生成signature签名;
而认证字符串的关键组成部分,则为签名。其余的信息拼接函数,也在SignInfo类中体现。
定义请求的BaseRequest:
package com.huawei.client.rest.v2.demo.base; @Setter @Getter public class BaseRequest { private RequestHeader request; private Object msgBody; public BaseRequest(Object body) { this.request = new RequestHeader(); this.msgBody = body; } public BaseRequest(RequestHeader head, Object body) { this.request = head; this.msgBody = body; } public RequestHeader getRequest() { return this.request; } public void setRequest(RequestHeader request) { this.request = request; } public Object getMsgBody() { return this.msgBody; } public void setMsgBody(Object msgBody) { this.msgBody = msgBody; } }
其中,RequestHeader如下:
package com.huawei.client.rest.v2.demo.base; public class RequestHeader { /** * 版本号 */ private String version = "2.0"; public String getVersion() { return this.version; } public void setVersion(String version) { this.version = version; } }
定义请求的BaseResponse:
package com.huawei.client.rest.v2.demo.base; @Setter @Getter public class BaseResponse { final static int SUCCESS = 0; private ResponseHead resultHead; private Object resultData; public BaseResponse() { } public BaseResponse(String resultCode, String resultMsg, Object resultData) { this.resultHead = new ResponseHead(resultCode, resultMsg); this.resultData = resultData; } public BaseResponse(ResponseHead resultHead, Object resultData) { this.resultHead = resultHead; this.resultData = resultData; } }
其中,ResponseHead如下:
package com.huawei.client.rest.v2.demo.base; public class ResponseHead { private String resultCode; /** * 响应结果 */ private String resultMsg; public ResponseHead() { } public ResponseHead(String resultCode, String resultMsg) { this.resultCode = resultCode; this.resultMsg = resultMsg; } public String getResultCode() { return this.resultCode; } public void setResultCode(String resultCode) { this.resultCode = resultCode; } public String getResultMsg() { return this.resultMsg; } public void setResultMsg(String resultMsg) { this.resultMsg = resultMsg; } }
buildSignHttpHead()函数的具体实现参见ToolUtils:
package com.huawei.client.rest.v2.demo.utils; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import com.alibaba.fastjson.JSONObject; import com.huawei.client.rest.v2.demo.HttpProxyHelper; import com.huawei.client.rest.v2.demo.base.BaseRequest; import com.huawei.client.rest.v2.demo.config.CmsParameters; import com.huawei.client.rest.v2.demo.sign.SignInfo; /** * Rest接口认证摘要算法工具类 */ public class ToolUtils { /** * 由字符串转换成文件工具 * @param in * @param filePath * @param fileName * @return */ public static boolean saveToFileByStr(InputStream in, String filePath, String fileName){ boolean flag = true; if(in != null){ try { // 将上面生成的文件格式字符串 fileStr,还原成文件显示 File file=new File(filePath,fileName); FileOutputStream fos=new FileOutputStream(file); try{ byte[] buffer = new byte[1024]; int reader = 0; while ((reader = in.read(buffer)) != -1) { fos.write(buffer, 0, reader); } fos.flush(); } finally { fos.close(); in.close(); } } catch (Exception e) { flag = false; e.printStackTrace(); } } return flag; } /** * 传入.csv文件路径 * 返回读取到的原始录音文件地址工具 * @param filePath * @return */ public static List<String> readFile(String filePath){ List<String> originalFileList = new ArrayList<String>(); try { BufferedReader reader = new BufferedReader(new FileReader(filePath)); //第一行为表头信息 reader.readLine(); String line = null; while((line=reader.readLine())!=null){ String item[] = line.split(",");//CSV格式文件为逗号分隔符文件,这里根据逗号切分 originalFileList.add(item[item.length-4]); } } catch (Exception e) { e.printStackTrace(); } return originalFileList; } /** * 实际客户端开发根据JSON框架直接转换BaseRequest为Json串 * @param request * @return String * @throws Exception */ public static String toJsonString(BaseRequest request) throws Exception { JSONObject jsonObject = new JSONObject(); StringBuilder buffer = new StringBuilder(); buffer.append("{"); buffer.append("\"request\":"); buffer.append(jsonObject.toJSONString(request.getRequest())); buffer.append(","); buffer.append("\"msgBody\":"); buffer.append(jsonObject.toJSONString(request.getMsgBody())); buffer.append("}"); return buffer.toString(); } /** * 构造鉴权字段信息 * @param httpPath * @param bodyJson * @return * @throws InvalidKeyException * @throws NoSuchAlgorithmException * @throws UnsupportedEncodingException */ public Map<String, String> buildSignHttpHead(String httpPath, String bodyJson) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException { // 参与签名鉴权的head字段,host字段是必须的 Map<String, String> signedHeaders = new HashMap<>(); signedHeaders.put(SignInfo.HEAD_HOST, CmsParameters.host); signedHeaders.put(SignInfo.HEAD_CONTENT_LENGTH, String.valueOf(bodyJson.getBytes("UTF-8").length)); signedHeaders.put(SignInfo.HEAD_CONTENT_TYPE, "application/json;charset=UTF-8"); // 鉴权数据接口设置 SignInfo signInfo = new SignInfo(); signInfo.setAccessKey(CmsParameters.accessKey); signInfo.setSecretKey(CmsParameters.secretKey); signInfo.setPayload(bodyJson); signInfo.setTimestamp(new Date()); signInfo.setHttpMethod(HttpProxyHelper.HTTP_METHOD_POST); signInfo.setUri(httpPath); signInfo.setSignedHeaders(this.lowerCaseSignedHeaders(signedHeaders)); // 生成签名 String signature = signInfo.authString(); Map<String, String> httpHeads = new HashMap<>(signedHeaders); // 追加到HTTPHEAD,发送到服务器端 httpHeads.put(SignInfo.HEAD_AUTHORIZATION, signature); return httpHeads; } /** * 参与计算的signedHeaders进行转换 * @param * @return Map<String, String> */ private Map<String, String> lowerCaseSignedHeaders(Map<String, String> signedHeaders) { if ((null == signedHeaders) || signedHeaders.isEmpty()) { throw new IllegalArgumentException("signedHeaders cann't be null."); } Map<String, String> headers = new HashMap<>(); for (Entry<String, String> e : signedHeaders.entrySet()) { String name = e.getKey(); String value = e.getValue(); headers.put(name.toLowerCase(Locale.ENGLISH), value.trim()); } if (!signedHeaders.containsKey(SignInfo.HEAD_HOST)) { throw new IllegalArgumentException("signedHeaders must has host."); } return headers; } }
- 代码实现:
- 建立工程的main函数:
public static void main(String[] args) { HttpClientMain demo = new HttpClientMain(); try { demo.cmsPingTest(); } catch (Exception e) { e.printStackTrace(); } }
- 实现接口(包含鉴权)的post请求的主函数如下:
public void cmsPingTest() throws Exception { HttpProxyHelper httpProxy = new HttpProxyHelper(); //请求接口的URL final String httpPath = "/rest/cmsapp/v1/ping"; final String postUrl = CmsParameters.url + httpPath; //构造请求接口中的bodyJson JSONObject jsonBody = new JSONObject(); jsonBody.put("say", "Hello world!"); String jsonBodyStr = JSONObject.toJSONString(jsonBody); // 构造鉴权信息 Map<String, String> httpHeads = ToolUtils.buildSignHttpHead(httpPath, jsonBodyStr); // 执行HTTPS请求,获取返回JSON字符串 String response = httpProxy.doPost(postUrl, httpHeads, jsonBodyStr); //调用成功后,会返回包含responseId的信息 System.out.println(response); }
- 实现接口(包含鉴权)的get请求的主函数如下(对比post请求,get请求的其他代码不变):
public void cmsPingTest() throws Exception { HttpProxyHelper httpProxy = new HttpProxyHelper(); //请求接口的URL final String httpPath = "/rest/cmsapp/v1/ping"; final String postUrl = CmsParameters.url + httpPath; // GET请求参数 Map<String, String> queryParameters = new HashMap<>(); // GET请求构造鉴权信息 Map<String, String> httpHeads = ToolUtils.buildSignHttpHead(httpPath, null, queryParameters, HttpProxyHelper.HTTP_METHOD_GET); System.out.println("httpHeads:" + httpHeads); // 执行HTTPS请求,获取返回JSON字符串 String response = httpProxy.doGet(postUrl, httpHeads, queryParameters); //调用成功后,会返回包含responseId的信息 System.out.println(response); }
- 函数ToolUtils.toJsonString()的实现,实际的调用者开发应根据JSON框架直接转换BaseRequest为JSON串,此处仅作为样例:
/** * 实际客户端开发根据JSON框架直接转换BaseRequest为Json串 * @param request * @return String * @throws Exception */ public static String toJsonString(BaseRequest request) throws Exception { JSONObject jsonObject = new JSONObject(); StringBuilder buffer = new StringBuilder(); buffer.append("{"); buffer.append("\"request\":"); buffer.append(jsonObject.toJSONString(request.getRequest())); buffer.append(","); buffer.append("\"msgBody\":"); buffer.append(jsonObject.toJSONString(request.getMsgBody())); buffer.append("}"); return buffer.toString(); }
- Https请求POST方法的实现,实际的调用者也可自有选择框架,使用其自带的POST方法:
package com.huawei.client.rest.v2.demo; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class HttpProxyHelper { public static final String HTTP_METHOD_POST = "POST"; public String doPost(String urlAddress, Map<String, String> httpHeads, String bodyJson) throws IOException, NoSuchAlgorithmException, KeyManagementException { byte[] byteData = bodyJson.getBytes("UTF-8"); OutputStream out = null; InputStream in = null; // 建立连接 this.initHttpsURLConnection(); URL url = new URL(urlAddress); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); // 设置参数 httpConn.setRequestMethod(HttpProxyHelper.HTTP_METHOD_POST); httpConn.setRequestProperty("Charset", "UTF-8"); //httpConn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); httpConn.setRequestProperty("accept", "application/json"); //httpConn.setRequestProperty("Content-Length", String.valueOf(byteData.length)); httpConn.setDoOutput(true); httpConn.setDoInput(true); httpConn.setUseCaches(false); httpConn.setConnectTimeout(20 * 1000); httpConn.setReadTimeout(30 * 1000); // 设置业务携带参数 if ((null != httpHeads) && !httpHeads.isEmpty()) { for (Entry<String, String> e : httpHeads.entrySet()) { httpConn.setRequestProperty(e.getKey(), e.getValue()); } } try { // 发送数据 out = httpConn.getOutputStream(); out.write(byteData); out.flush(); // 接收数据 int responseCode = httpConn.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { throw new RuntimeException("Failed responseCode " + responseCode); } in = httpConn.getInputStream(); String reponseJson = this.getStreamAsString(in, "UTF-8"); return reponseJson; } finally { this.closeStream(out); this.closeStream(in); } } /** * 从流获取字符串 * @param in * @param charset * @return String * @throws IOException */ private String getStreamAsString(InputStream in, String charset) throws IOException { BufferedInputStream buffer = new BufferedInputStream(in); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] cache = new byte[512]; int count = 0; while ((count = buffer.read(cache)) > 0) { out.write(cache, 0, count); } } finally { if (buffer != null) { buffer.close(); } } return new String(out.toByteArray(), charset); } /** * 关闭流 * @param stream */ private void closeStream(Closeable stream) { if (null != stream) { try { stream.close(); } catch (Exception e) { e.printStackTrace(); } } } private void initHttpsURLConnection() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslcontext = SSLContext.getInstance("SSL"); HostnameVerifier hnv = new HttpsHostnameVerifier(); sslcontext.init(null, new TrustManager[] { new HttpsTrustAnyTrustManager() }, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(hnv); } private class HttpsTrustAnyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } } private class HttpsHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } }
- 建立工程的main函数:
- 变量参数展示表:
变量
值
HttpHead
Host="10.22.26.181:28080"
Content-Length="22"
Content-Type="application/json;charset=UTF-8"
accessKey
globalaktest
secretKey
1qaz**********************************20
HttpURI
/rest/cmsapp/v1/ping
HttpMethod
POST
timestamp
2018-10-17T11:48:24Z
HttpBody
{"say": "Hello world!"}
SignedHeaders
content-length;content-type;host
CanonicalHeaders
content-length:22\n
content-type:application%2Fjson%3Bcharset%3DUTF-8\n
host:10.22.26.181%3A28080
CanonicalRequest
POST\n
/rest/cmsapp/v1/ping\n
content-length;content-type;host\n
content-length:22\n
content-type:application%2Fjson%3Bcharset%3DUTF-8\n
host:10.22.26.181%3A28080\n
%7B%22request%22%3A%7B%22version%22%3A%222.0%22%7D%2C%22msgBody%22%3A%7B%22accountId%22%3A%22%22%2C%22beginTime%22%3A%222018-06-29%2010%3A42%3A49%22%2C%22endTime%22%3A%222018-07-02%2010%3A42%3A49%22%2C%22agentId%22%3A%22%22%2C%22callId%22%3A%22%22%2C%22dataType%22%3A%22call_record%22%2C%22callBackURL%22%3A%22http%3A%2F%2F10.57.118.171%3A8080%22%7D%7D
SigningKey
b25b************************************************40
Signature
d5a8************************************************2f
Authorization
auth-v2/globalak/2018-10-17T11:48:24Z/content-length;content-type;host/d5a8************************************************2f
- 如何调通第一个接口: