文档首页> > API参考> 话单数据接口> 鉴权算法

鉴权算法

分享
更新时间: 2019-04-25 19:39

所有接口服务器端都会对客户端的请求数据做鉴权签名验证,鉴权流程如下:

调用者生成Authorization的生成规则内容如下:

各内容的算法和规则

  • CcfsParameters
    说明:

    该参数类为CCFS二次开发demo中的参数汇总,在接下来的描述、调用中,会直接取该类中的参数。使用者只需统一修改该参数类,即可完成相应的鉴权配置。

    以下是demo中基本的参数样例,调用者可根据具体情况,增加相应的参数,以方便后续的项目管理:

    /**
     * CCFS接口请求参数类定义
     */
    public class CcfsParameters
    {
    	//以下信息需要根据实际信息替换
    	/**
    	 * host访问地址
    	 */
    	public static final String host = "10.5.1.3:8443";
    	public static final String url = "https://" + host;
    
    	/**
    	 * 开发者ID
    	 */
    	public static final String accessKey= "1c69fe9c059e41*****d4e09e053ce1";
    
    	/**
    	 * 开发者秘钥(未加密)
    	 */
    	public static final String secretKey = "Ws$2*****3^%A463%0";
    
    }
  • 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'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).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
    说明:

    调用者对HTTP请求中的Header部分进行选择性编码,也可以自行决定哪些Header参与编码,唯一要求是Host域必须被编码,但headname=“Authorization”的head不能参与编码计算,因为Authorization是最终鉴权head域。

    备注:Host的值为对应服务的https地址中的ip:port ,也可以为域名。

    大多数情况下,我们推荐对以下Header进行编码:

    Host="10.5.1.13:8443"  
    Content-Length="214"
    Content-Type="application/json;charset=UTF-8"
    • 内容计算规则:

      遍历参与编码的HttpHead里面的Header name

      1. 把Header name都改为小写,即调用lowerCase()函数
      2. 把上面Header name转换后的字符后面追加分割符";",生成一条记录,注意最后一个字段不追加";";
      3. 把上面所有记录按照字典排序,然后按照顺序连接成一个大字符串。
    • 代码实现:
      1. 构建signedHeaders的Map,填充所需字段信息,然后调用lowerCaseSignedHeaders函数进行字符处理:
        // 参与签名鉴权的head字段,host字段是必须的
        Map<String, String> signedHeaders = new HashMap<>();
        signedHeaders.put(SignInfo.HEAD_HOST, CcfsParameters.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(CcfsParameters.accessKey);
        signInfo.setSecretKey(CcfsParameters.secretKey);
        signInfo.setPayload(bodyJson);
        signInfo.setTimestamp(new Date());
        signInfo.setHttpMethod(HttpProxyHelper.HTTP_METHOD_POST);
        signInfo.setUri(httpPath);
        signInfo.setSignedHeaders(lowerCaseSignedHeaders(signedHeaders));
        

        以上函数的完整代码参见:1.如何调通第一个接口:

      2. 函数: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;
        }	
    • 具体例子:
      例子:参与编码的HttpHead如下:
      Host="10.5.1.13:8443"  
      Content-Length="214"
      Content-Type="application/json;charset=UTF-8"
      经过上述规则处理后为:
      SignedHeaders=content-length;content-type;host
  • CanonicalHeaders
    说明:

    其要求计算的编码规则与SignedHeaders一致,但增加了head value的编码。

    • 内容计算规则:

      遍历参与编码的HttpHead里面的head name

      1. 把Header name都改为小写,即调用lowerCase()函数
      2. 调用NormalizePath函数,对刚才转换后的小写字符串进行格式化;
      3. 格式化后的字符串+":"+NormalizePath((Header value).trim()),生成一条记录字符串;
      4. 把上面的记录按照字典排序,进行排序;
      5. 遍历排序后的记录,中间追加字符串"\n"连接成一个大的字符串:

      最后一条记录不追加"\n"。

      例子:参与编码的HttpHead如下:
      Host="10.5.1.13:8443"  
      Content-Length="214"
      Content-Type="application/json;charset=UTF-8"
      经过上述规则处理后CanonicalHeaders为:
      content-length:214\n
      content-type:application%2Fjson%3Bcharset%3DUTF-8\n
      host:10.5.1.13%3A8443
  • CanonicalRequest
    • 内容计算规则:
      CanonicalRequest = $HttpMethod + "\n" + $HttpURI+ "\n" + SignedHeaders($HttpHeaders) + "\n" + CanonicalHeaders ($HttpHeaders) + "\n" + NormalizePath($HttpBody)

      参数描述:

      1. $HttpMethod,指HTTP协议中定义的GET、PUT、POST等请求,必须使用全大写的形式。所涉及的HTTP Method有:GET、POST、PUT、DELETE、HEAD,但当前BPOMS服务仅支持POST这1种;
      2. $HttpURI,指接口请求的http URI,比如完整url为https://10.5.1.13:8443/CCFS/resource/ccfs/queryBillData那么HttpURI就为/CCFS/resource/ccfs/queryBillData ,必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”;
      3. $HttpBody:是通过HTTP BODY体提交到服务器端的字符串,该字符串的形式为标准JSON串,具体字段见各接口的定义。
    • 代码实现:

      参见•SignInfo中的canonicalRequest()函数。

  • 生成认证字符串Authorization
    • 内容计算规则:
      1. 首先生成authStringPrefix和SigningKey
        1. 关于authStringPrefix和SigningKey的生成规则:
          authStringPrefix="auth-v2/{accessKey}/{timestamp}/{SignedHeaders}";
          SigningKey = sha256Hex(secretKey, authStringPrefix);

          说明:

          auth-v2:鉴权版本号,当期版本为固定字符串“auth-v2”;

          accessKey:BPOMS平台分配给调用者的鉴权ID;

          secretKey:BPOMS平台分配给调用者的鉴权密钥;

          timestamp:调用者端生成的UTC时间,时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss'Z'";

        2. 代码实现:authStringPrefix

          生成authStringPrefix的函数在SignInfo类的如下两个函数中实现

          authStringPrefix()
          appendSignedHeaders(StringBuilder buffer)

        3. 代码实现:SigningKey:

          参见SignInfo类的authString()中,

          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;
          	}	
          }

      1. 生成签名Signature
        1. 签名的生成规则:
          Signature = sha256Hex(SigningKey, CanonicalRequest)
        2. 代码实现:包含加密函数:sha256Hex

          参见上述:c.代码实现:SigningKey:SigningKey中的sha256Hex()实现方法。

      2. 认证字符串
        1. 认证字符串的生成规则:
          Authorization:$authStringPrefix/$Signature
        2. 代码实现:其中,signedHeaders为上述signedHeaders模块中构建的Map。
          // 生成签名
          String signature = signInfo.authString();
          Map<String, String> httpHeads = new HashMap<>(signedHeaders);
          // 追加到HTTPHEAD,发送到服务器端
          httpHeads.put(SignInfo.HEAD_AUTHORIZATION, signature);

      认证字符串要通过HttpHead的“Authorization”域,随请求发送给BPOMS服务端。

  • 完整例子
    1. 如何调通第一个接口:
      说明:

      调通一个接口,鉴权是必要的一环。

      在该项目中,鉴权入口在于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.huawei.client.rest.v2.demo.HttpProxyHelper;
      import com.huawei.client.rest.v2.demo.base.BaseRequest;
      import com.huawei.client.rest.v2.demo.config.CcfsParameters;
      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
      	{
      		JsonHelper jsonHelper = new JsonHelper();
      		StringBuilder buffer = new StringBuilder();
      		buffer.append("{");
      		buffer.append("\"request\":");
      		buffer.append(jsonHelper.toJsonString(request.getRequest()));
      		buffer.append(",");
      		buffer.append("\"msgBody\":");
      		buffer.append(jsonHelper.toJsonString(request.getMsgBody()));
      		buffer.append("}");
      
      		return buffer.toString();
      	}
      
      	/**
      	 * 仅作demo演示: 手动生成回调URL,模拟回到服务器接收callBackURL
      	 * 生成规则: yyyyMMdd(当天日期)_{responseId}
      	 * @param responseId
      	 * @return
      	 */
      	public static String getCallBackUrl(String responseId){
      		Date currentTime = new Date();
      		SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
      		String dateString = formatter.format(currentTime);
      		String callBackUrl = dateString + "_" + responseId + CcfsParameters.zipType;
      		return callBackUrl;
      	}
      
      	/**
      	 * 构造鉴权字段信息
      	 * @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, CcfsParameters.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(CcfsParameters.accessKey);
      		signInfo.setSecretKey(CcfsParameters.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;
      	}
      }
    2. 代码实现:
      • 建立工程的main函数:
        public static void main(String[] args)
        	{
        		HttpClientMain demo = new HttpClientMain();
        		
        		try
        		{
        			demo.ccfsQueryBillData();
        		}
        		catch (Exception e)
        		{
        			e.printStackTrace();
        		}
        	}
      • 实现接口(包含鉴权)的主函数如下:
        public void ccfsQueryBillData() throws Exception
        {
        	//请求接口的URL
        	final String httpPath = "/CCFS/resource/ccfs/queryBillData";
        	final String postUrl = CcfsParameters.url + httpPath;
        	
        	//构造请求接口中的bodyJson
        	CcfsQueryBillData query = new CcfsQueryBillData();
        	query.setBeginTime("2018-06-29 10:42:49");
        	query.setEndTime("2018-07-02 10:42:49");
        	query.setCallBackURL("http://10.57.118.171:8080");
        	query.setDataType("call_record");
        	BaseRequest request = new BaseRequest(query);
        	final String bodyJson = this.toJonString(request);
        	
        	// 构造鉴权信息
        	Map<String, String> httpHeads = this.buildSignHttpHead(httpPath, bodyJson);
        	
        	// 执行HTTP请求,获取返回JSON字符串
        	String response = this.httpProxy.doPost(postUrl, httpHeads, bodyJson);
        	
        	//调用成功后,会返回包含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
        {
        	JsonHelper jsonHelper = new JsonHelper();
        	StringBuilder buffer = new StringBuilder();
        	buffer.append("{");
        	buffer.append("\"request\":");
        	buffer.append(jsonHelper.toJsonString(request.getRequest()));
        	buffer.append(",");
        	buffer.append("\"msgBody\":");
        	buffer.append(jsonHelper.toJsonString(request.getMsgBody()));
        	buffer.append("}");
        
        	return buffer.toString();
        }
      • Http请求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;
        		}
        	}
        }
    3. 变量参数展示表:

      变量

      HttpHead

      Host="10.5.1.13:8443"?

      Content-Length="214"

      Content-Type="application/json;charset=UTF-8"

      accessKey

      BpomstestId_1

      secretKey

      Y6ks0W9eL4oda}dP

      HttpURI

      /CCFS/resource/ccfs/queryBillData

      HttpMethod

      POST

      timestamp

      2018-10-17T11:48:24Z

      HttpBody

      {"request":{"version":"2.0"},"msgBody":{"accountId":"","beginTime":"2018-06-29 10:42:49","endTime":"2018-07-02 10:42:49","agentId":"","callId":"","dataType":"call_record","callBackURL":"http://10.57.118.171:8080"

      SignedHeaders

      content-length;content-type;host

      CanonicalHeaders

      content-length:214\n

      content-type:application%2Fjson%3Bcharset%3DUTF-8\n

      host:10.5.1.13%3A8443

      CanonicalRequest

      POST\n

      /CCFS/resource/ccfs/queryBillData\n

      content-length;content-type;host\n

      content-length:214\n

      content-type:application%2Fjson%3Bcharset%3DUTF-8\n

      host:10.5.1.13%3A8443\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

      b25b933582eb4dfc756c4dbee7faac39befae8571a6130825faebf0a64376540

      Signature

      d5a8119a9b02a44aa928aaac21ee702166620f5cd0dc97cdeace359af1e88e2f

      Authorization

      auth-v2/BpomstestId_1/2018-10-17T11:48:24Z/content-length;content-type;host/d5a8119a9b02a44aa928aaac21ee702166620f5cd0dc97cdeace359af1e88e2f

如果您喜欢这篇文档,您还可以:

文档是否有解决您的问题?

提交成功!

非常感谢您的反馈,我们会继续努力做到更好!

反馈提交失败,请稍后再试!

*必选

请至少选择或填写一项反馈信息

提交反馈 取消

如您有其它疑问,您也可以通过华为云社区问答频道来与我们联系探讨

跳转到云社区